WWW FAQs: How do I add an email contact form to my website?


2007-06-26: Visitors to your website want to contact you. You need a way to help them do that.

At first it seems simple— just design an HTML form. But then you're stumped. What happens next? How do you take that form data and do something meaningful?

There are easy fixes and sneaky workarounds for this problem. But those methods have major flaws for serious applications. Certainly you can provide a link to send you email with a simple mailto: link like this one:


<a href="mailto:myaccount@mydomain.com">Send Me Email</a>

But this approach results in massive quantities of incoming spam (junk email), as automated software harvests your email address and begins transmitting garbage you have no desire to read.

Also, this approach doesn't allow you to control the format of the user's message in any way. You can't prevent them from sending attachments, nor can you require them to select the product they are interested in from a drop-down list or request other helpful information from the user.

Another well-known shortcut is the mailto: form submission:


<form action="mailto:myaccount@mydomain.com">
<p>
<i>Please select the product you are interested in.</i>
</p>
<select name="product">
<option value="widget1">Widget #1</option>
<option value="widget2">Widget #2</option>
<option value="widget3">Widget #3</option>
</select>
<p>
<i>Please enter the text of your message in the field that follows.</i>
</p>
<textarea
  name="body"
  rows="10"
  cols="60"></textarea>

<p>
<input type="submit" name="send" value="Send Your Message"/>
<input type="submit" name="cancel" value="Cancel - Never Mind"/>
</p>
</form>

This approach does give us a bit more control: the user is invited to select the product they are interested in, in addition to entering a message.

Unfortunately, there are several major problems with the mailto: form action strategy:

1. The user's browser might not be set up to send forms via email correctly.

2. The format in which the message actually reaches you varies from browser to browser, making it very difficult to read or organize the results.

3. Internet Explorer displays a warning to the user that email is about to be sent. Most users will just hit "Cancel" when they see warnings of this sort.

The mailto: approach might be handy for beginners who know just enough HTML to design a form. But for those of us who want to do professional work, there's a better way.

Handling Email Forms With PHP

"Real" form submissions— forms that transmit their data back to the web server, rather than directly sending email— are one of the most powerful features of HTML. By transmitting the user's message to the web server, we can control exactly how that message is handled— verifying that the user's entries are correct, requiring them to re-enter incorrect fields, and storing or emailing the final result in any way we see fit. Though this particular article focuses on email, we could just as easily choose to store the user's form submission in a database. Or store it in a database and email a notification to the webmaster. As the designer, the choice is up to you.

"Great! What HTML do I use to accomplish all this?"

Well, that's the catch: HTML is a markup language, not a programming language. So if you have been searching for "the right HTML tags" to process a form submission, then your frustration is understandable.

Fortunately, you're definitely not the first person to want to make a website do more than deliver "static" HTML pages! So web servers have long since been extended in a variety of ways to allow "dynamic" behavior such as handling form submissions. Currently the most popular solution is PHP.

PHP is a simple programming language that can easily be "sprinkled into" a web page to add new capabilities like handling form submissions. Unlike earlier approaches, such as CGI (Common Gateway Interface) programming, PHP puts the web page first and lets you add a little bit of PHP code at a time as your needs grow more complex. And that makes PHP easier to learn.

Learning PHP

PHP is too big a subject for a single article. And that's why I've written a separate article on it. See my article how can I receive form submissions? for a whirlwind introduction to PHP. That article also links to several more detailed tutorials that teach PHP programming in greater depth.

The rest of this article will focus on an example of a useful PHP page that allows the user to send email to the webmaster. We'll take advantage of the CAPTCHA system presented in my article how do I add a CAPTCHA to my web page? to make life difficult for spammers. And we'll use the Accountify system introduced in my article how do I add accounts to my website? to pre-fill the "email address" and "real name" blanks on the form and skip the CAPTCHA for users who have logged into accounts on our website. Both of these features are optional and you can use the form without them. In particular, websites that have no other use for accounts probably won't want to use Accountify just for a single page.

The Basics: A Bare-Bones Email Form With PHP

Here's a dead-simple web page, myform.html, that submits form data to a PHP page called takeform.php:


<html>
<head>
<title>Send Us Email</title>
</head>
<body>
<form action="takeform.php" method="POST">
<p>
<input
name="email"
size="64"
maxlength="64"/>
<b>Your</b> Email Address
</p>
<p>
<input
name="realname"
size="64"
maxlength="64"/>
Your Real Name (<i>so our reply won't get stuck in your spam folder</i>)
</p>
<p>
<input
name="subject"
size="64"
maxlength="64"/>
Subject Of Your Message
</p>
<p>
<i>Please enter the text of your message in the field that follows.</i>
</p>
<textarea
name="body"
rows="10"
cols="60">
</textarea>
<p>
<input type="submit" name="send" value="Send Your Message"/>
</p>
</form>
</body>
</html>

And here is the code for takeform.php, a PHP page that accepts the form submission and delivers it to the webmaster:


<html>
<head>
<title>Thanks For Contacting Us</title>
</head>
<body>
<?php
  // Change this to YOUR address
  $recipient = 'webmaster@example.com';
  $email = $_POST['email'];
  $realName = $_POST['realname'];
  $subject = $_POST['subject'];
  $body = $_POST['body'];
  # We'll make a list of error messages in an array
  $messages = array();
# Allow only reasonable email addresses
if (!preg_match("/^[\w\+\-.~]+\@[\-\w\.\!]+$/", $email)) {
$messages[] = "That is not a valid email address.";
}
# Allow only reasonable real names
if (!preg_match("/^[\w\ \+\-\'\"]+$/", $realName)) {
$messages[] = "The real name field must contain only " .
"alphabetical characters, numbers, spaces, and " .
"reasonable punctuation. We apologize for any inconvenience.";
}
# CAREFUL: don't allow hackers to sneak line breaks and additional
# headers into the message and trick us into spamming for them!
$subject = preg_replace('/\s+/', ' ', $subject);
# Make sure the subject isn't blank afterwards!
if (preg_match('/^\s*$/', $subject)) {
$messages[] = "Please specify a subject for your message.";
}

$body = $_POST['body'];
# Make sure the message has a body
if (preg_match('/^\s*$/', $body)) {
$messages[] = "Your message was blank. Did you mean to say " .
"something?";
}
  if (count($messages)) {
    # There were problems, so tell the user and
    # don't send the message yet
    foreach ($messages as $message) {
      echo("<p>$message</p>\n");
    }
    echo("<p>Click the back button and correct the problems. " .
      "Then click Send Your Message again.</p>");
  } else {
    # Send the email - we're done
mail($recipient,
      $subject,
      $body,
      "From: $realName <$email>\r\n" .
      "Reply-To: $realName <$email>\r\n");
    echo("<p>Your message has been sent. Thank you!</p>\n");
  }
?>
</body>
</html>

That covers the basics nicely. takeform.php accepts the user's input, makes sure their input is reasonable and not dangerous by using regular expressions, and if there are no errors, sends an email message to the webmaster. Exactly what we wanted to do. Even so, it's a bit more complicated than you probably expected. And it's fair to ask why that is necessary.

Safe Programming: Never Trust The User!

"Why do we have to use those ugly regular expressions? What's all this preg_match stuff about?"

Naive programmers often simply write:


$email = $_POST['email'];
# BLINDLY TRUSTING THE USER - NEVER DO THIS!
mail($recipient,
  $subject,
  $body,
  # Set the "From:" line via the "extra fields" option of mail()
  "From: $email");

This is very dangerous. No matter what your HTML says, crackers can easily submit whatever they like to your PHP page. And if they choose to submit text formatted like this for the email field:


fake@fake.com
Bcc: victim1@site.com, victim2@site.com, victim3@site.com...

Then your incorrectly written PHP code will obediently transmit copies of the message to all of the innocent victims on that Bcc: line. That potentially very long Bcc: line. In other words, your site becomes a spam relay. Before too long, someone complains and your web host shuts you down. But by then your reputation has already been damaged.

How do we fix this? By never, ever allowing "newline" characters (carriage returns and line feeds) to appear in any field entered by the user, except for the message body.

That's just one of the many things we're checking for in those regular expressions. This code matches a reasonable email address:


/^[\w\+\-\.\~]+\@[\-\w\.\!]+$/

Yes, I know: regular expressions look like swear words in a comic strip! But just calm down and read it from left to right:

The / signals the start of a Perl-compatible regular expression in PHP.

The ^ character, at the beginning of a regular expression, means "matching at the very start of the string." Without this the regular expression would match if a reasonable email address appeared anywhere inside the user's input— but the user could also enter newlines, commas and additional email addresses. Once again, your site is transmitting spam for unwelcome parasites.

Next, the [ and ] characters enclose a set of characters that are valid to appear at this point in the regular expression. We begin the set with \w, which matches any "word character"— letters, numbers, and underscores. We also include the + sign (escaped with \ so it's clear that we mean the + character itself), the - sign (also escaped), the "period" character . (escaped as it would otherwise mean "any character"), and the tilde character.

Afte the ] that closes the set, we see a + sign. This means "repeated one or more times." We are saying that the email address must begin with one or more of the characters in the set.

Next we have the @ sign that separates the username from the domain name in the email address. This too is escaped, just in case it has some special significance in a regular expression! I don't pretend to know every feature of regular expressions either— so I recommend escaping punctuation characters whenever they are meant literally. Otherwise they may have surprising effects.

The second half of the regular expression matches the domain name part of the email address with another character set. Here we allow only letters, digits, dashes, periods, and exclamation points. Including exclamation points is a bit nostalgic— only old-fashioned UUCP addresses use them. Who knows, someone somewhere might still be using those.

The final $ character is just as important as the leading ^: it means "matching at the end of the string." Again, without this, the regular expression would match if an email address appeared anywhere in the string (or, if ^ was present at the beginning, at the start of the string with more characters allowed afterward). That would still allow crackers to add creative and unpleasant email headers of their own. And that is never a good thing.

Last but not least, / marks the end of a Perl-compatible regular expression. If we needed to, we could follow this with option letters like s that specify case-insensitivity and other features.

This is just a quick overview of some of the useful features of regular expressions seen in this particular page. For more information, see the article using Perl-compatible regular expressions with PHP on zend.com.

Handling Errors Elegantly

So let's say the user did make a mistake. They left a field blank, or they entered an invalid email address. We can't accept their form submission as-is.

The code above simply displays the error messages and then invites the user to click the "back" button and edit their work.

This works, but it's not very elegant. Better-designed sites display error messages at the same time they re-display the form, with all of the user's input still there and ready to be edited as painlessly as possible.

Can we do that? Sure we can! But to do it well, we'll need to take the task of displaying the form in the first place away from a separate HTML page, and give it to our PHP page.

Fortunately, PHP is very good at this sort of thing. We're not limited to inserting bits and pieces of PHP code into our HTML. We can also insert bits and pieces of HTML into our PHP— without putting everything in clunky echo statements.

Here's the secret: you can shift in and out of "PHP mode" with <?php and ?> at any time. Including in the middle of a PHP function or if { } block. And when you do, that HTML is output to the user only when that if { } block succeeds or that function is called.

So what does that mean for us? It means that we can easily write a displayForm function that outputs the form only when the user has not yet submitted the form— or when errors are present in their original form submission.

By looking for particular form fields, such as named submit buttons, we can decide which function should be called. And that determines what the end user will see on that particular visit to the page.

Here's the code, at the top of the PHP page, that decides whether to call displayForm, sendMail or redirect:


<?php
# The above must be the VERY FIRST LINE or the
# redirect function can't work
if ($_POST['send']) {
  sendMail();
} elseif (($_POST['cancel']) || ($_POST['continue'])) {
  redirect();
} else {
  displayForm(false);
}

The sendMail function, which is called when the user has clicked the "send" button, delivers the email... unless there are errors. In that case, it calls displayForm instead, like this:


function sendMail()
{
  # [Check for errors goes here]
  if (count($messages) {
    # Errors, redisplay form with errors
    displayForm($messages);
  } else {
    # No errors, send the message
  }
}

The displayForm function shifts back into "HTML mode" to display the actual form. Before doing that, though, it fetches the fields the user has already entered so that they can be easily re-displayed, saving the user the trouble of typing them again. However, we take care to call htmlspecialchars to make sure any characters with special significance in HTML, such as <, > and ", are correctly "escaped" to prevent the user from tricking our site into displaying unwanted HTML elements or executing JavaScript code in the browser:


function displayForm($messages)
{
  $escapedEmail = htmlspecialchars($_POST['email']);
  $escapedRealName = htmlspecialchars($_POST['realname']);
  $escapedSubject = htmlspecialchars($_POST['subject']);
  $escapedBody = htmlspecialchars($_POST['body']);
  # Shift back into HTML mode to send the form
?>
<html>
<head>
<title>Contact Us</title>
</head>
<body>
<h1>Contact Us</h1>
<?php
  # Shift back into PHP mode for a moment to display
  # the error message, if there was one
  if (count($messages) > 0) {
    $message = implode("<br>\n", $messages);
    echo("
  }
?>
<form method="POST" action="<?php echo $_SERVER['DOCUMENT_URL']?>">
<p>
<input
  name="email"
  size="64"
  maxlength="64"
  value="<?php echo $escapedEmail?>"/>
  <b>Your</b> Email Address
</p>
<!-- Additional fields here, handled the same way, with
  echo commands to insert them -->    
<p>
<input type="submit" name="send" value="Send Your Message"/>
<input type="submit" name="cancel" value="Cancel - Never Mind"/>
</p>
</form>
</body>
</html>
<?php
}

The Page That Talks To Itself

"Hey, wait a minute! What's that $_SERVER['DOCUMENT_URL'] stuff about?"

As I've explained, this PHP page does double duty: it displays the form, and it sends the actual email when the form is submitted. So when we click on the "Send" button, where should the form data go? Back to the very same PHP page!

We could just write:


<form method="POST" action="mypage.php">

But a more general solution, safe to copy and paste into other pages, is to get the name of the page from PHP itself. The $_SERVER associative array is chock-full of useful information, such as the full name of the website ( $_SERVER['SERVER_NAME'] ). And it also contains the full path of the current web page within the site ( $_SERVER['DOCUMENT_URL'] ). Using this technique means that we don't have to change the name of the page in multiple places in the code if we choose to rename the PHP file later.

After The Ball Is Over: Redirecting the User

Many contact forms suffer from the same inelegant "feature:" when you are through sending your message, you are left staring at a thank-you page with no obvious way to get back to where you came from. And that's not ideal.

Most of the time, the user's experience looks something like this:

1. The user looks at an interesting product or service.

2. The user wants more information.

3. The user clicks on a "contact" link, displaying the contact form.

4. The user submits the form.

5. The user would like to immediately return to the product page. But they can't conveniently do that, because the form submission page doesn't know where they came from.

Fortunately we can solve this problem too! Earlier I mentioned the $_SERVER variable, and the wide variety of useful information kept within. And one of the most useful values stored here is $_SERVER['HTTP_REFERER'], which contains the address of the page the user came from to reach the current page.

How can we make this work for us? By carrying this information along with us on each form submission until the email is finally sent. Here's the relevant code from the displayForm function:



  $returnUrl = $_POST['returnurl'];
  if (!strlen($returnUrl)) {
    # We'll return the user to the page they came from
    $returnUrl = $_SERVER['HTTP_REFERER'];
    if (!strlen($returnUrl)) {
      # Stubborn browser won't give us a referring
      # URL, so return to the home page of our site instead
      $returnUrl = '/';
    }  
  }
  ...
  $escapedReturnUrl = htmlspecialchars($returnUrl);
  ...
  <input
    type="hidden"
    name="returnurl"
    value="<?php echo $escapedReturnUrl?>"/>

How does this work? The first time through, we don't have a setting for the returnurl form field yet. So we take the "referring URL" from $_SERVER['HTTP_REFERER']. Then we escape it in the usual way with htmlspecialchars and pass it along via a form field.

"But I don't want the user to see an ugly form field with a referring URL!"

No, of course not. That's why we use a hidden form field to do the job. An input element with its type attribute set to hidden is not actually shown to the user. Instead, its value is passed unmodified when the form is submitted.

So far, so good! But where do we put this information to use? In the redirect function, which is called in two situations:

1. When the user clicks the "Cancel" button.

2. When the user clicks the "Continue" button... the one and only visible element of a form that appears after we thank the user for contacting us.

The redirect function simply calls PHP's built-in header function to send the user back to the right page:


function redirect()
{
  $returnUrl = $_POST['returnurl'];
  header("Location: $returnUrl");
}

Important: the header function must be called before any other output is sent. This is why <?php must be the very first line in the entire .php file: if any stray HTML appears before that point, including a blank line, PHP will automatically transmit the standard headers for a plain old web page... not a redirect. Once that happens, it's too late to redirect the user.

The final version of my email.php code, presented at the end of this article, has a slightly more elaborate redirect function that checks to be sure the return URL points to our own site. You might argue that this is unnecessary— all the user can do, after all, is trick themselves into going somewhere— and you're probably right. But it never hurts to guard against unexpected ways that code might be used in the future.

Preventing Spam: CAPTCHA Me If You Can

Once upon a time, a web-based contact form without security holes was sufficient protection against spammers, who preferred to harvest ordinary email addresses. These days, though, you're bound to receive spam from software that sends unwanted messages to web-based contact forms, too... just in case someone is listening.

Ugh! What can be done?

There is no 100% perfect solution to the spam problem. But we can make the spammers' lives more difficult with a CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart). CAPTCHA systems slow down spammers by presenting a question that, with any luck, only a human being can answer. A typical CAPTCHA displays rotated text on a complicated background and, if it is well-designed, also provides an audio option for blind users. For more information about CAPTCHAs in general and a freely available CAPTCHA system, see my articles what is a CAPTCHA? and how do I add a CAPTCHA to my website?

Here I'll briefly show how to incorporate my CAPTCHA system into our web-based contact form.

First, we'll need to use PHP's require statement to bring the CAPTCHA code in, before anything else has been output to the user:


<?php
# DON'T output any HTML or blank lines before the above
require '/path/to/captcha/captcha.php';

Of course, the path may need adjustment to allow for where you have installed my captcha.php code on your site.

This code does most of the work for us. All we have to do now is display the CAPTCHA prompt at the appropriate point in the form:


<p>
<b>Please help us prevent fraud</b> by entering the code displayed in the
image in the text field. Alternatively,
you may click <b>Listen To This</b> to hear the code spoken aloud.
</p>
<p>
  <img src="<?php echo captchaImgUrl()?>"/>
  <input name="captcha" size="8"/>
  <a href="<?php echo captchaWavUrl()?>">Listen To This</a>
</p>

This code tells the browser to fetch both the image and, if the user clicks on "Listen To This," the audio version of the CAPTCHA by making new requests for the very same page... but with special parameters that invoke the CAPTCHA code, which catches this special situation, generates the image or sound required and immediately exits.

There's just one detail left: checking the user's entry in the CAPTCHA field for correctness. We do that in the sendMail function, along with the rest of our checks:


if ($_POST['captcha'] != $_SESSION['captchacode']) {
  $messages[] = "You did not enter the security code, " .
    "or what you entered did not match the code. " .
    "Please try again.";
}

You'll note that the CAPTCHA code is available in the $_SESSION associative array. My CAPTCHA system uses PHP's built-in session handling to store the CAPTCHA code. Your PHP configuration does need to support the session handling feature.

For this and other reasons, I strongly recommend reading the installation instructions in my article how do I add a CAPTCHA to my web form? before deciding to implement this feature.

Accounts: Why Type Anything Twice?

The contact form is already quite useful and meets the expectations of "guests"— users who are not regular visitors to your website. But those who do visit your site regularly might appreciate a more convenient contact form, one that remembers their real name and email address and does not require them to respond to a CAPTCHA every time.

Can we do that? Yes— and if the design of our website includes many places where users might otherwise have to reenter such information, it begins to make good sense to remember it. If only there were a convenient way to add accounts to the website, so that users could log in and have their personal details instantly recalled.

As you have probably guessed, there is a convenient way. We can add accounts to the website easily using Accountify, a free PHP-based solution that stores account information in a database and can be easily added to existing web pages. With Accountify, PHP's session support becomes account support. Anything we store in the $_SESSION associative array becomes permanent for that user and is recalled each time they log in again. And since $_SESSION['realname'] and $_SESSION['email'] are standard in Accountify, it is particularly convenient for use in a contact form.

But how do we add Accountify to our contact form? It's not hard. But first, you should definitely read my article how do I add accounts to my website? and follow the installation instructions for Accountify itself.

Once you have Accountify up and running, it's easy enough to incorporate it into the contact form. First, just like the CAPTCHA, Accountify must be brought in with a require command at the top of the page:


<?php
# DON'T output any HTML or blank lines before the above
require '/path/to/accountify/login.php';

This provides Accountify with the opportunity to turn on session handling and load the user's account information into the session if they have already logged in.

Now we can easily fetch the user's real name and email address from Accountify in our displayForm function:


# At the BEGINNING of displayForm: bring in
# Accountify's login object
global $login;

# Early in the body of the page: display the login prompt
$login->prompt();

# Fetch the user's email address and real name if they
# haven't already been set. We do this AFTER
# calling $login->prompt();
if (!strlen($escapedEmail)) {
  $escapedEmail = htmlspecialchars($_SESSION['email']);
}

if (!strlen($escapedRealName)) {
  $escapedRealName = htmlspecialchars($_SESSION['realname']);
}

We'll also want to bring in Accountify's style sheet to give the login prompt a reasonable appearance. I recommend copying Accountify's chrome/login.css file to your main web folder, so that you can use this line in the head element of each page that features Accountify:


<link href="/login.css" rel="stylesheet" type="text/css">

"What if I want Accountify and CAPTCHA?"

Yes, you can have both! But make sure you require Accountify first. Also, you will want to edit the guestFields option in Accountify's login_config.php file so that the CAPTCHA field is not deleted when a user logs out:


'guestFields' => array (
  'captchacode'
)

Now you can decide whether or not to display the CAPTCHA based on whether the user is still logged in or not. You can do that simply by testing $_SESSION['id']. If $_SESSION['id'] is not false, the user is logged in; don't bother displaying the CAPTCHA or checking $_SESSION['captchaCode']. If $_SESSION['id'] is false, it's a good idea to display the CAPTCHA because the user is only a guest. Code for this is in the complete version of email.php at the end of this article. That version also uses PHP's function_exists function to figure out whether we've decided to require Accountify and CAPTCHA. That way, my code works regardless of whether or not you decide to uncomment those require commands.

Complete Contact Form PHP Code

I've discussed quite a bit of code above, but for space reasons I haven't included every detail. Instead, I've made the complete version of email.php available for download here in a convenient zip file.

Conclusion

Accepting user input and sending email to the administrator is a deceptively simple task. Though PHP's mail() function is simple and effective, correctly checking for potential security problems takes time and care. And preventing spam and other abuses of the contact form takes doing as well. I have presented a complete solution that addresses these issues. Hopefully you'll find it useful on your own site!

Legal Note: yes, you may use sample HTML, Javascript, PHP and other code presented above in your own projects. You may not reproduce large portions of the text of the article without our express permission.

Got a LiveJournal account? Keep up with the latest articles in this FAQ by adding our syndicated feed to your friends list!