Log in

PHP contact form

For twenty years the answer to a contact form was a PHP script calling mail(). It still works, and it still has the same three problems it always had. Here is the script, an honest account of what breaks, and the version with no PHP in it at all.

The rendered result. The code below is the source — it is not run inside this preview.

Replace YOUR_FORM_ID with a form of your own and this sends to your inbox.

Create your form
send.php — the classic version
<?php
// The classic version. It needs PHP hosting, a configured MTA, and it will
// land in spam more often than not because the mail is unauthenticated.
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $name    = filter_input(INPUT_POST, "name", FILTER_SANITIZE_SPECIAL_CHARS);
    $email   = filter_input(INPUT_POST, "email", FILTER_VALIDATE_EMAIL);
    $message = filter_input(INPUT_POST, "message", FILTER_SANITIZE_SPECIAL_CHARS);

    if (!$email) {
        http_response_code(422);
        exit("Invalid email address");
    }

    // Header injection lives here. Never interpolate a raw field into a header.
    $headers = "From: site@example.com\r\n" .
               "Reply-To: " . $email . "\r\n";

    mail("you@example.com", "New enquiry from " . $name, $message, $headers);
    header("Location: /thank-you.html");
    exit;
}
contact.html — the replacement
<form action="https://submit.formpost.ai/YOUR_FORM_ID" method="POST">
  <h2>Contact us</h2>

  <label for="name">Name</label>
  <input id="name" type="text" name="name" required>

  <label for="email">Email</label>
  <input id="email" type="email" name="email" required>

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="4" required></textarea>

  <button type="submit">Send message</button>
</form>

What actually goes wrong with mail()

  • Deliverability. mail() hands the message to the local MTA, which sends from your web server's IP with no SPF alignment and no DKIM signature. Gmail and Outlook increasingly treat that as spam — and you never find out, because mail() returns true regardless.
  • It returns true for queued, not delivered. The function tells you the MTA accepted the message. Everything after that — rejection, bounce, silent drop — is invisible to your script.
  • Header injection. Interpolating a raw field into the headers lets somebody put a newline and a Bcc: in your email field and use your server to send mail. The example above validates the address first, which is the minimum.
  • It needs PHP hosting. Which rules out GitHub Pages, Netlify, Vercel static output, S3, and every other place a static site would rather live.

The replacement is the HTML you already have

Delete send.php, change the action to an endpoint, and the form is done. The sending happens somewhere that is set up for it — authenticated domains, retries, bounce handling — and your site goes back to being static files.

<form action="https://submit.formpost.ai/YOUR_FORM_ID" method="POST">

Everything the PHP was doing has an equivalent that needs no code. The redirect after success is a hidden field or a form setting. Validation is the required and type attributes. Reply-to is set automatically from the field named email. Spam filtering is a honeypot and an optional captcha, configured rather than written.

If you are keeping the PHP

Use a proper mail library — PHPMailer or Symfony Mailer — and send through an authenticated SMTP provider rather than the local MTA. That fixes deliverability and gives you a real error when sending fails. Keep filter_input with FILTER_VALIDATE_EMAIL on anything that reaches a header, and never interpolate user input into one directly.

Moving away from PHP does not mean moving away from your host. The form is still your HTML on your server — only the sending moved.

Questions about this contact form

Do I need PHP for a contact form?
No. PHP was only ever there to send the email. Point the form's action at an endpoint that sends it for you and the page can be a static HTML file on any host.
Why do emails from PHP mail() go to spam?
They are sent from your web server's IP with no SPF alignment and no DKIM signature, so receiving providers cannot verify them. This gets worse every year as bulk-sender requirements tighten.
What is email header injection?
Putting a newline plus an extra header into a field that gets interpolated into the mail headers — typically to add a Bcc and relay spam through your server. Validate the address before it touches a header, and never build headers from raw input.
Is mail() returning true enough to confirm delivery?
No. It means the local mail transfer agent accepted the message for queuing. Rejections, bounces and silent drops all happen after that point and your script never hears about any of them.

Related examples

Or go back to all 21 contact form examples.

Give this form an endpoint

Formpost takes the submission, emails it to you and keeps a searchable copy. Free for 250 messages a month, with unlimited forms and no card.