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.
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:
<?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:
| Problem | Why it happens |
|---|---|
| No PHP on the host | GitHub 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 vanishes | mail() 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 kept | If 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.
- 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.
- 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.
Move the form off PHP
Keep the field names exactly as they are. Only the action changes, plus two hidden inputs:
<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><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.php | Now |
|---|---|
$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: $email | Automatic, 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 wrote | The _gotcha honeypot, spam scoring, rate limits and field validation |
- Add your domain under Allowed domains.
_redirectonly works for anhttps:URL on one of those domains. - Delete
contact.phpfrom 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.phpURL no longer exists. Bots that post straight to it get a404, which is fine.
Check it works
- Under the form's Allowed domains, add your site's domain, plus
localhostwhile you develop. Leave it empty to accept posts from anywhere. - Submit the form on your live page. You land on your old
thanks.html. - Open Submissions in the dashboard. The new submission is there with every field you sent.
- 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.
- Press Reply. The visitor's address is in the To line.
- 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 namedemailor 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
_subjectis 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
| Problem | What 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 spam | The 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 file | A 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 arrived | It 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
Yes. The old script keeps working until you change the form's action, so you can test the new endpoint on a copy of the page first, then switch and delete the script.
Yes. Point the form's action at a form backend instead of a PHP script. The backend receives the post and emails you, so your site can be plain HTML on any static host.
Scripts usually set the From header to the visitor's address, but the mail leaves your host's server. That fails the visitor domain's SPF and DMARC checks, so Gmail and Outlook treat it as forged. A form backend sends from its own domain and puts the visitor in Reply-To instead.
No. Ikiform stores whatever names you post. Keep the email field named email, email_address, your_email, reply_to, contact_email, or add a _replyto field, so replies go to the visitor.
It fixes deliverability if you send through a provider with your domain set up for SPF and DKIM. You still need a PHP host, validation, spam filtering and somewhere to keep the messages. It's a good choice if you already maintain PHP code.
Stuck on a step, or want a guide for your situation? Email hello@ikiform.com.