A Simple PHP Contact Form (That Doesn't Get You Hacked)

A Simple PHP Contact Form (That Doesn't Get You Hacked)

Tutorials forms php tutorial web development

So I've had like four people ask me this month how to make a "contact me" form that actually emails them, instead of just sitting there looking pretty and doing nothing. Apparently my "portfolio site with a form that goes nowhere" phase back in 2009 wasn't unique. So here's the version I actually use now, stripped down to the parts that matter.

This isn't fancy. No frameworks, no CodeIgniter, none of that. Just plain PHP, because honestly for a five-field contact form you don't need anything else. If you're building the next Basecamp, sure, go set up a framework. For this, it's overkill.

The HTML part

Nothing weird here, just a standard form posting to itself:

<form action="contact.php" method="post">
  <label>Name: <input type="text" name="name"></label>
  <label>Email: <input type="text" name="email"></label>
  <label>Message: <textarea name="message"></textarea></label>
  <input type="submit" value="Send it">
</form>

The PHP part, and why validation actually matters

Here's the thing people skip, and it's the thing that bites you later. If you just take whatever's in $_POST and shove it straight into mail(), you're basically begging some bored guy in his mom's basement to turn your contact form into a spam cannon. This is a real thing, it's called email header injection, and it's exactly as dumb as it sounds — someone puts a bunch of \r\nBcc: [email protected] junk into your name field and suddenly your innocent little form is blasting out Viagra ads to half of Nigeria's inbox. Not a hypothetical. I've seen server logs.

So, basic validation:

<?php
$name = trim($_POST['name']);
$email = trim($_POST['email']);
$message = trim($_POST['message']);

$errors = array();

if (empty($name)) {
    $errors[] = "You forgot your name.";
}

if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors[] = "That doesn't look like a real email address.";
}

if (empty($message)) {
    $errors[] = "Kind of hard to email you an empty message.";
}

// this is the important part, strip anything that looks like a header
if (preg_match("/(content-type|bcc:|cc:|to:|mime-version)/i", $name . $email . $message)) {
    $errors[] = "Nice try.";
}

if (!empty($errors)) {
    foreach ($errors as $e) {
        echo "<p>" . htmlspecialchars($e) . "</p>";
    }
} else {
    $to = "[email protected]";
    $subject = "New contact form message";
    $body = "Name: $name\nEmail: $email\n\nMessage:\n$message";
    $headers = "From: [email protected]";

    mail($to, $subject, $body, $headers);
    echo "<p>Thanks, I'll get back to you.</p>";
}
?>

That filter_var with FILTER_VALIDATE_EMAIL is one of those things I didn't know about for way too long, I used to write some horrible regex for email validation that I copy-pasted from a forum in like 2007 and never questioned. Turns out PHP just has a built-in for it. Feels obvious in hindsight, most useful things do.

Couple other notes if you're actually going to use this on a real site:

  • Always run output through htmlspecialchars() before echoing anything back to the page, otherwise someone puts <script> tags in the name field and now you've got an XSS hole sitting right next to your spam hole. Fun combo.
  • The regex check up there is not bulletproof, it's a basic tripwire. If you want something more serious eventually look at PHPMailer, which handles headers properly and doesn't just concatenate strings and pray. I haven't bothered switching my own site over yet because it's a five-field form, not a SaaS product, but I probably should.
  • Don't put your real "From" address in there as the visitor's email, some mail servers will flag it as spoofing and bounce the whole thing. Learned that one the hard way on a client site last year, spent two hours convinced my server was broken when it was Gmail being suspicious of me impersonating strangers.
  • A honeypot field (a hidden input that real users never fill in, but bots always do) will cut down on garbage submissions more than you'd expect, for basically zero effort.

Anyway that's the whole thing, maybe forty lines of actual logic. It's not glamorous, nobody's writing a TechCrunch piece about your contact form, but it works, it's not a security disaster, and it took less time to build than it took me to write this post. Which, given how long I spent picking a font for the error messages, is not actually saying much.