PHP Email Contact Form

Contact forms are a pretty effective alternative to mailto links spread around webpages. They’re easy to use for users and harder to figure out for email harvesters. As you would expect, building an effective contact form in PHP isn’t hard at all. We’re going to explain the process step by step in this article.

What we’ll basically want to do is:

  • Build a simple HTML contact form (with optional CSS) and embed it on our website
  • Write a PHP script that will effectively handle sending emails
  • Save the inserted details into a spreadsheet
  • Send an autoresponder to a user, letting them know we’ll handle their request

We’ll also explain how to secure the form with reCaptcha and validate emails, as well as how to handle common errors. So, let’s begin.

Building a simple PHP contact form

For starters, we’ll need to build a simple form with just HTML. If you don’t care much about the visual side of it, it can be as simple as this:

<form>
<h2>Contact us</h2>
<p><label>First Name:</label> <input name="myEmail" type="text" /></p>
<p><label>Email Address:</label> <input style="cursor: pointer;" name="myEmail" type="text" /></p>
<p><label>Message:</label>  <textarea name="message"></textarea> </p>
<p><input type="submit" value="Send" /></p>
</form>

Of course, without any CSS it looks really ugly:

But it will be just fine for demonstration purposes. If you’re not into writing CSS at the moment, you can use any of the hundreds of available form builders. Some options include Simfatic, 123FormBuilder, and PHP Jabbers. CodeCanyon has hundreds of tools with reviews for each to make the choice easier.  

Okay, we’ve got the contact form, but whatever data users insert goes straight into a black hole. So, we’ll need to add two more elements to the form–ACTION, and METHOD. ACTION tells the browser what it should do when a user hits the ‘Send’ button. METHOD tells it how to approach this move.

In our case, we’ll want to load a new PHP page in the background that we’ll talk about in the next chapter. Since we’ll be processing the data, we’re legally obliged to protect the user’s details (name and email address), making the POST method a safer option. Using GET would mean these details get included in the URL of the following page, something we’d rather avoid.

All we need to do now is include these two attributes in the code we previously used:

<form method="POST" action="form.php" id="contact-form">
<h2>Contact us</h2>
<p><label>First Name:</label> <input name="name" type="text" /></p>
<p><label>Email Address:</label> <input style="cursor: pointer;" name="email" type="text" /></p>
<p><label>Message:</label>  <textarea name="message"></textarea> </p>

<p><input type="submit" value="Send" /></p>
</form>

Inspect Your Emails

Data Validation

To get rid of some spammers, but also to protect your users from accidentally mistyping their contact details, it’s worth adding some validation algorithms to the contact form. For the highest chance of success, consider doing this on both the client- and server-side. 

Client-side validation will quickly return any errors on the frontend, letting a user fix them right away. Server-side validation will also catch those that passed the initial test (by, for example disabling JavaScript in the browser) but shouldn’t have.

While you can write your own script, it’s often worth using what’s already been built and tested. We will use a bulletproof solution for schema validation – https://validatejs.org/. For simplicity, just add a library from a CDN.

<script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
<script>
   const constraints = {
       name: {
           presence: { allowEmpty: false }
       },
       email: {
           presence: { allowEmpty: false },
           email: true
       },
       message: {
           presence: { allowEmpty: false }
       }
   };

   const form = document.getElementById('contact-form');

   form.addEventListener('submit', function (event) {
     const formValues = {
         name: form.elements.name.value,
         email: form.elements.email.value,
         message: form.elements.message.value
     };

     const errors = validate(formValues, constraints);

     if (errors) {
       event.preventDefault();
       const errorMessage = Object
           .values(errors)
           .map(function (fieldValues) { return fieldValues.join(', ')})
           .join("\n");

       alert(errorMessage);
     }
   }, false);
</script>

For the server-side validation, you can use the following code:

<?php

$errors = [];

if (!empty($_POST)) {
   $name = $_POST['name'];
   $email = $_POST['email'];
   $message = $_POST['message'];
  
   if (empty($name)) {
       $errors[] = 'Name is empty';
   }

   if (empty($email)) {
       $errors[] = 'Email is empty';
   } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
       $errors[] = 'Email is invalid';
   }

   if (empty($message)) {
       $errors[] = 'Message is empty';
   }
}

If either verification fails, it would be a good idea to let the user know. You can use the following code to build an error message:

<?php
if (!empty($errors)) {
   $allErrors = join('<br/>', $errors);
   $errorMessage = "<p style='color: red;'>{$allErrors}</p>";
}

You are free to render this message anywhere on your page. 

PHP code to send email from a contact form

Our form is leading somewhere, but it’s not clear where. Let’s add some action points and use the default mail() function to send a simple email after submission.

The code of this PHP contact form specifies the headers and body of a message and sends each email with the mail() method. It also includes the validations we explained in the previous chapter and the HTML form itself.

<?php

$errors = [];
$errorMessage = '';

if (!empty($_POST)) {
    $name = $_POST['name'];
    $email = $_POST['email'];
    $message = $_POST['message'];

    if (empty($name)) {
        $errors[] = 'Name is empty';
    }

    if (empty($email)) {
        $errors[] = 'Email is empty';
    } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Email is invalid';
    }

    if (empty($message)) {
        $errors[] = 'Message is empty';
    }


    if (empty($errors)) {
        $toEmail = 'example@example.com';
        $emailSubject = 'New email from your contant form';
        $headers = ['From' => $email, 'Reply-To' => $email, 'Content-type' => 'text/html; charset=iso-8859-1'];

        $bodyParagraphs = ["Name: {$name}", "Email: {$email}", "Message:", $message];
        $body = join(PHP_EOL, $bodyParagraphs);

        if (mail($toEmail, $emailSubject, $body, $headers)) {
            header('Location: thank-you.html');
        } else {
            $errorMessage = 'Oops, something went wrong. Please try again later';
        }
    } else {
        $allErrors = join('<br/>', $errors);
        $errorMessage = "<p style='color: red;'>{$allErrors}</p>";
    }
}

?>

<html>
<body>
  <form action="/mail_form.php" method="post" id="contact-form">
    <h2>Contact us</h2>

    <?php echo((!empty($errorMessage)) ? $errorMessage : '') ?>
    <p>
      <label>First Name:</label>
      <input name="name" type="text" value="dima"/>
    </p>
    <p>
      <label>Email Address:</label>
      <input style="cursor: pointer;" name="email" value="dima@dima.com" type="text"/>
    </p>
    <p>
      <label>Message:</label>
      <textarea name="message">dima</textarea>
    </p>

    <p>
      <input type="submit" value="Send"/>
    </p>
  </form>
  <script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
  <script>
      const constraints = {
          name: {
              presence: { allowEmpty: false }
          },
          email: {
              presence: { allowEmpty: false },
              email: true
          },
          message: {
              presence: { allowEmpty: false }
          }
      };

      const form = document.getElementById('contact-form');

      form.addEventListener('submit', function (event) {
          const formValues = {
              name: form.elements.name.value,
              email: form.elements.email.value,
              message: form.elements.message.value
          };

          const errors = validate(formValues, constraints);

          if (errors) {
              event.preventDefault();
              const errorMessage = Object
                  .values(errors)
                  .map(function (fieldValues) {
                      return fieldValues.join(', ')
                  })
                  .join("\n");

              alert(errorMessage);
          }
      }, false);
  </script>
</body>
</html>

If the PHP contact form returns error 500, double check to see if you specified the parameters of the mail() function properly. Make sure the mail server is properly configured on your machine.

The mail() function doesn’t support external SMTP servers, and this is a serious bottleneck. As emails are sent from your own servers rather than those of reputable ESPs (Email Sending Providers), they will frequently be going to spam.

PHP contact forms, of course, allow for different methods of sending emails. Arguably the most popular choice for sending emails in PHP is PHPMailer. If you’re not familiar with it, we’ve got it covered in detail in our PHPMailer Guide.

Here’s how PHPMailer code would look for this page:

<?php

use PHPMailer\PHPMailer\PHPMailer;
require __DIR__ . '/vendor/autoload.php';

$errors = [];
$errorMessage = '';

if (!empty($_POST)) {
    $name = $_POST['name'];
    $email = $_POST['email'];
    $message = $_POST['message'];

    if (empty($name)) {
        $errors[] = 'Name is empty';
    }

    if (empty($email)) {
        $errors[] = 'Email is empty';
    } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Email is invalid';
    }

    if (empty($message)) {
        $errors[] = 'Message is empty';
    }


    if (!empty($errors)) {
        $allErrors = join('<br/>', $errors);
        $errorMessage = "<p style='color: red;'>{$allErrors}</p>";
    } else {
        $mail = new PHPMailer();

        // specify SMTP credentials
        $mail->isSMTP();
        $mail->Host = 'smtp.mailtrap.io';
        $mail->SMTPAuth = true;
        $mail->Username = 'd5g6bc7a7dd6c7';
        $mail->Password = '27f211b3fcad87';
        $mail->SMTPSecure = 'tls';
        $mail->Port = 2525;

        $mail->setFrom($email, 'Mailtrap Website');
        $mail->addAddress('piotr@mailtrap.io', 'Me');
        $mail->Subject = 'New message from your website';

        // Enable HTML if needed
        $mail->isHTML(true);

        $bodyParagraphs = ["Name: {$name}", "Email: {$email}", "Message:", nl2br($message)];
        $body = join('<br />', $bodyParagraphs);
        $mail->Body = $body;

        echo $body;
        if($mail->send()){

            header('Location: thank-you.html'); // redirect to 'thank you' page
        } else {
            $errorMessage = 'Oops, something went wrong. Mailer Error: ' . $mail->ErrorInfo;
        }
    }
}

?>

<html>
<body>
  <form action="/swiftmailer_form.php" method="post" id="contact-form">
    <h2>Contact us</h2>

    <?php echo((!empty($errorMessage)) ? $errorMessage : '') ?>
    <p>
      <label>First Name:</label>
      <input name="name" type="text" value="dima"/>
    </p>
    <p>
      <label>Email Address:</label>
      <input style="cursor: pointer;" name="email" value="dima@dima.com" type="text"/>
    </p>
    <p>
      <label>Message:</label>
      <textarea name="message">dima</textarea>
    </p>

    <p>
      <input type="submit" value="Send"/>
    </p>
  </form>
  <script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
  <script>
      const constraints = {
          name: {
              presence: {allowEmpty: false}
          },
          email: {
              presence: {allowEmpty: false},
              email: true
          },
          message: {
              presence: {allowEmpty: false}
          }
      };

      const form = document.getElementById('contact-form');

      form.addEventListener('submit', function (event) {
          const formValues = {
              name: form.elements.name.value,
              email: form.elements.email.value,
              message: form.elements.message.value
          };

          const errors = validate(formValues, constraints);

          if (errors) {
              event.preventDefault();
              const errorMessage = Object
                  .values(errors)
                  .map(function (fieldValues) {
                      return fieldValues.join(', ')
                  })
                  .join("\n");

              alert(errorMessage);
          }
      }, false);
  </script>
</body>
</html>

There are several other reliable tools that can handle the task as well. Check out our guide to sending emails in PHP for more details.

Try Mailtrap for Free

By all means, use mail() for testing, or if getting a contact form response every now and then is all you need from mailing functionality. But if you’re going to send lots of transactional emails, it’s worth looking for a more reliable alternative. 

PHP contact form with Google reCaptcha

To add one more layer of security, you may want to add a simple reCaptcha script to your PHP mail form. You can do this in a very simple way. 

First of all, head to https://www.google.com/recaptcha/admin/create and fill out the form you’ll find there. You can choose between reCaptcha v2 and v3 (v1 is no longer supported). To make it simple, we’ll opt for the former. 

ReCaptcha v2 is sent with a form, and we can easily handle it in the backend with the rest of the form fields. ReCaptcha v3, on the other hand, needs to be called manually on the frontend. This is doable but would require us to rewrite the entire code. Let’s leave this for another occasion.

Submit the form and you’ll see your individual Site Key and Secret Key.

And here’s the form with the added ReCaptcha:

<?php

$errors = [];
$errorMessage = '';

$secret = 'your secret key';

if (!empty($_POST)) {
    $name = $_POST['name'];
    $email = $_POST['email'];
    $message = $_POST['message'];
    $recaptchaResponse = $_POST['g-recaptcha-response'];

    $recaptchaUrl = "https://www.google.com/recaptcha/api/siteverify?secret={$secret}&response={$recaptchaResponse}";
    $verify = json_decode(file_get_contents($recaptchaUrl));

    if (!$verify->success) {
      $errors[] = 'Recaptcha failed';
    }

    if (empty($name)) {
        $errors[] = 'Name is empty';
    }

    if (empty($email)) {
        $errors[] = 'Email is empty';
    } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Email is invalid';
    }

    if (empty($message)) {
        $errors[] = 'Message is empty';
    }


    if (!empty($errors)) {
        $allErrors = join('<br/>', $errors);
        $errorMessage = "<p style='color: red;'>{$allErrors}</p>";
    } else {
        $toEmail = 'example@example.com';
        $emailSubject = 'New email from your contant form';
        $headers = ['From' => $email, 'Reply-To' => $email, 'Content-type' => 'text/html; charset=iso-8859-1'];

        $bodyParagraphs = ["Name: {$name}", "Email: {$email}", "Message:", $message];
        $body = join(PHP_EOL, $bodyParagraphs);

        if (mail($toEmail, $emailSubject, $body, $headers)) {
            header('Location: thank-you.html');
        } else {
            $errorMessage = "<p style='color: red;'>Oops, something went wrong. Please try again later</p>";
        }
    }
}

?>

<html>
<body>
  <script src="https://www.google.com/recaptcha/api.js"></script>
  <form action="/form.php" method="post" id="contact-form">
    <h2>Contact us</h2>

    <?php echo((!empty($errorMessage)) ? $errorMessage : '') ?>
    <p>
      <label>First Name:</label>
      <input name="name" type="text"/>
    </p>
    <p>
      <label>Email Address:</label>
      <input style="cursor: pointer;" name="email" type="text"/>
    </p>
    <p>
      <label>Message:</label>
      <textarea name="message"></textarea>
    </p>

    <p>
      <button
        class="g-recaptcha"
        type="submit"
        data-sitekey="your site key"
        data-callback='onRecaptchaSuccess'
      >
        Submit
      </button>
    </p>
  </form>
  <script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
  <script>
      const constraints = {
          name: {
              presence: {allowEmpty: false}
          },
          email: {
              presence: {allowEmpty: false},
              email: true
          },
          message: {
              presence: {allowEmpty: false}
          }
      };

      const form = document.getElementById('contact-form');

      form.addEventListener('submit', function (event) {
          const formValues = {
              name: form.elements.name.value,
              email: form.elements.email.value,
              message: form.elements.message.value
          };

          const errors = validate(formValues, constraints);

          if (errors) {
              event.preventDefault();
              const errorMessage = Object
                  .values(errors)
                  .map(function (fieldValues) {
                      return fieldValues.join(', ')
                  })
                  .join("\n");

              alert(errorMessage);
          }
      }, false);

      function onRecaptchaSuccess () {
          document.getElementById('contact-form').submit()
      }
  </script>
</body>
</html>

Storing responses in Google Spreadsheets

Since we only want to save a few values somewhere, we can just populate a Google Sheet with the results. Let’s connect Zapier’s webhooks with a spreadsheet. For a start, don’t forget to specify table columns, which the app will use to recognize where to put data sent by our form. 

Create a new Zap that will connect the webhook and spreadsheets, following the instructions in Zapier. They are super clear, so you will be able to figure it out on your own. In the app, your Zap will look like this.

Use this code to send a request to Zapier and see how your spreadsheet is populating after each form submission.

$zapierWebhookUrl = '<your-webhook-url>';
$ch = curl_init($zapierWebhookUrl);
$payload = json_encode(['email' => $email, 'name' => $name, 'message' => $message]);
curl_setopt( $ch, CURLOPT_POSTFIELDS, $payload );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_exec($ch);
curl_close($ch);

Wrapping up

Do you sometimes wonder if a contact form is the right approach to email obfuscation? Maybe there are more reliable approaches to filtering out spammers and making life easier for our users? We discuss various approaches to the problem in our article on email obfuscation.

One last note. You’re definitely testing your emails in some way. Maybe you’re setting up dummy accounts or spamming all your colleagues with test emails? Perhaps you lean towards masking emails in your database and trying out your workflows this way? Whatever your choice, we recommend trying a different approach. 

Mailtrap is used by nearly half a million devs to test emails in a pre-production environment. It captures your test emails and lets you inspect them in a single inbox, with zero risk of spamming real users by accident. Curious about how it would work for you? Try it out for free.

Add comment

E-mail is already registered on the site. Please use the Login form or enter another.

You entered an incorrect username or password

Sorry, you must be logged in to post a comment.

2 comments

by Newest
by Best by Newest by Oldest

how can i download this codes?

This is an excellent post. This is really helpful for my business websites. It saved a lot of time.