Send a form to your email

Send a form to your email6 min read

Send an HTML form to email without PHP

Replace a contact.php script that calls mail() with a form that posts to a hosted endpoint, keeping your field names, your thank-you page and replies.

Preet SutharFounder, Ikiform

Change the form's action from your PHP script to a form backend's URL, keep the field names, and delete the script. The backend does what mail() did, plus the parts it never did well: spam filtering, storage and mail that doesn't land in spam. It works on static hosts where PHP can't run at all.

What's different here

Most PHP contact forms are the same twenty lines, copied from a tutorial years ago. They read $_POST, build a string, call mail(), and redirect. Here's a typical one:

contact.php
<?php
$name = $_POST["name"];
$email = $_POST["email"];
$phone = $_POST["phone"];
$message = $_POST["message"];

$to = "hello@example.com";
$subject = "Website enquiry from $name";
$body = "Name: $name\nEmail: $email\nPhone: $phone\n\n$message";
$headers = "From: $email\r\nReply-To: $email";

if (mail($to, $subject, $body, $headers)) {
  header("Location: /thanks.html");
} else {
  echo "Sorry, something went wrong.";
}

It looks harmless, and it worked when it was written. Today it breaks in four ways, and only the first is obvious:

ProblemWhy it happens
No PHP on the hostGitHub Pages, Netlify, Vercel, Cloudflare Pages and S3 serve files; they don't run PHP. The form posts to a file that is never executed, and you get a 404 or 405.
Mail lands in spam, or vanishesmail() hands the message to the server's local mail program. The From: header claims the visitor's address, but the mail leaves your host's server, so it fails the visitor domain's SPF and DMARC checks. Gmail and Outlook filter or reject it, often silently.
Header injection$email goes into the headers unchecked. A bot that posts a value with a line break can add its own Bcc: line and use your form to send spam, which gets your host's IP blocklisted.
Nothing is keptIf the email is lost, so is the message. There's no record, no spam filter and no way to look back at last month's enquiries.

You could fix each one: validate every value, send through an SMTP provider with a library such as PHPMailer, set up SPF and DKIM for your domain, add a honeypot and rate limit, and log to a database. That's a reasonable project if you already run PHP. If you'd rather not, move the work to a form backend.

The same form with PHP mail() and with a form backendwith contact.phpwith a form backendyour formaction="/contact.php"mail()From: the visitor,sent by your hostspam folderfails SPF andDMARCyour formsame fields, newactionIkiformfilters spam, storesityour inboxReply-To: thevisitorpostlocal mailpostemailThe same form with PHP mail() and with a form backendwith contact.phpwith a form backendyour formaction="/contact.php"mail()From: the visitor, sent by yourhostspam folderfails SPF and DMARCyour formsame fields, new actionIkiformfilters spam, stores ityour inboxReply-To: the visitorpostlocal mailpostemail
  1. With contact.php, the form posts to your host, and mail() sends a message that claims to come from the visitor. Receiving servers see a sender that doesn't match the domain and file it as spam or drop it.
  2. With a form backend, the form posts the same fields to Ikiform. Ikiform sends the email from its own domain and puts the visitor's address in Reply-To, which is what Reply-To is for.
The fix for deliverability isn't a better script. It's sending from a domain that is allowed to send.

Move the form off PHP

Keep the field names exactly as they are. Only the action changes, plus two hidden inputs:

Before
<form action="/contact.php" method="post">
  <input name="name" required />
  <input name="email" type="email" required />
  <input name="phone" type="tel" />
  <textarea name="message" required></textarea>
  <button>Send</button>
</form>
After
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <input name="name" required />
  <input name="email" type="email" required />
  <input name="phone" type="tel" />
  <textarea name="message" required></textarea>
  <input type="hidden" name="_redirect" value="https://example.com/thanks.html" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Send</button>
</form>

Then map each thing the script did to where it lives now:

In contact.phpNow
$to = "hello@example.com"Recipients under the form's Notifications settings
header("Location: /thanks.html")The _redirect hidden input, or Redirect after submit in the form's settings
Reply-To: $emailAutomatic, because the field is named email
$subject = "Website enquiry from $name"Fixed as New submission: <form name>. Name the form "Website enquiry" to keep the wording
echo "Sorry, something went wrong."Ikiform's error page for a plain post, or your own message with fetch
Checks you never wroteThe _gotcha honeypot, spam scoring, rate limits and field validation
  • Add your domain under Allowed domains. _redirect only works for an https: URL on one of those domains.
  • Delete contact.php from the server once the new form works. A forgotten script is still an open mail relay for bots that remember the URL.
  • If you moved to a static host, the old /contact.php URL no longer exists. Bots that post straight to it get a 404, which is fine.
If your form was a WordPress plugin rather than a script, the same idea applies. The WordPress guide shows where to put the form in a block theme.

Check it works

  1. Under the form's Allowed domains, add your site's domain, plus localhost while you develop. Leave it empty to accept posts from anywhere.
  2. Submit the form on your live page. You land on your old thanks.html.
  3. Open Submissions in the dashboard. The new submission is there with every field you sent.
  4. Open the email. It comes from Ikiform, not from the visitor's address, and isn't in spam. Add the sender to your contacts so later ones aren't either.
  5. Press Reply. The visitor's address is in the To line.
  6. Post with the honeypot filled in, _gotcha=bot. It still gets a success answer, but the submission is stored as spam and sends no email or webhook.

Gotchas

  • Old form names with brackets. PHP forms often use names like contact[email]. Ikiform stores them as they are, but the reply-to only comes from a field named email or a few close variants. Rename the field, or add _replyto.
  • The subject can't include the name. The script's subject line had the visitor's name in it. The notification subject is fixed, and _subject is passed to webhooks and the API without changing the email.
  • Character encoding. mail() scripts often mangled accents because they never set a charset. Ikiform's email is UTF-8, so names in any script arrive intact.
  • Multiple addresses in $to. A comma list in the script becomes separate recipients in the dashboard. Outside addresses confirm by link before they get anything. See multiple recipients.

If something goes wrong

ProblemWhat to do
The browser lands on Ikiform's thank-you page instead of yours_redirect is ignored unless its domain is in Allowed domains and it is an https: URL. Add the domain, or set Redirect after submit in the form's settings instead.
403 origin_not_allowed, or every post lands in spamThe page's domain isn't in the form's Allowed domains. Add it, plus localhost for local tests. Subdomains of a listed domain are allowed.
The form still posts to the PHP fileA cached page or a second copy of the form, such as a footer include, still has the old action. Search the site for contact.php.
The submission is in the dashboard but no email or webhook arrivedIt was stored as spam. Spam sends nothing. Open it under the Spam filter to see the reasons, and mark it as not spam to send its email and webhooks.

Questions

Stuck on a step, or want a guide for your situation? Email hello@ikiform.com.