Why Your Contact Form Email Never Arrives: SPF, DKIM and SMTP

Why Your Contact Form Email Never Arrives: SPF, DKIM and SMTP

A website that “is not working” is very often working perfectly. The contact form submits, shows a thank-you message, and stores nothing anywhere. The mail it tried to send had a From address at gmail.com, was handed to the hosting server’s local mail program, and was dropped by Gmail on arrival, because a shared web server in Noida has no business claiming to send mail as Gmail. Nine weeks of enquiries can disappear exactly that way, with a reassuring green tick shown to every person who submitted the form and nothing in the dashboard suggesting anything is wrong. It is one of the most common silent failures on small sites, and it is fixable in about half an hour once you understand what receiving servers are actually checking.

why the default setup fails

Flow: a WordPress form calls wp_mail, which sends through the shared host's unauthenticated local mail, and receiving providers reject or spam it.
The form shows ‘thank you’ either way. Nothing tells you the mail died.

WordPress sends mail through a PHP function called wp_mail(), which by default hands the message to whatever local mail program the server has. There is no authentication in that path and no delivery report coming back. The web server just puts the message down and walks away.

Receiving providers, meanwhile, have spent twenty years learning to distrust exactly that pattern. Mail from a shared hosting IP, with no cryptographic signature, claiming a sender domain it cannot prove it is allowed to use, looks indistinguishable from the junk they block all day. So it goes to spam or is refused outright, and your site reports success either way because PHP handed the message over without complaint.

what the receiving server is checking

Three email authentication checks: SPF lists allowed sending servers, DKIM signs messages and survives forwarding, and DMARC sets a failure policy and sends reports.
Knowing which answers what turns a pile of acronyms into a checklist.

Three standards do the work, and they answer different questions. Understanding which is which makes the rest of this straightforward instead of a pile of acronyms.

SPF answers “is this server allowed to send for this domain”

You publish a DNS record listing which servers may send mail using your domain. The receiver compares the connecting server against that list. SPF breaks the moment mail is forwarded, and it says nothing about whether the message was altered, which is why it is necessary but not sufficient.

DKIM answers “was this message really sent by that domain, unmodified”

Your sending service signs each message with a private key and you publish the matching public key in DNS. The receiver verifies the signature. This survives forwarding, and it is the record most small sites never publish because the SPF instructions were easier to follow.

DMARC answers “what should I do if those checks fail”

DMARC ties the other two to the visible From address and tells receivers your policy: monitor, quarantine, or reject. It also gives you aggregate reports, which is the only way you get feedback about mail claiming to be from your domain. Start in monitoring mode, read the reports for a few weeks, then tighten.

the actual fix: send through an authenticated relay

Table of email sending routes: a transactional service is best, a mailbox provider over SMTP suits low volume, local server mail rarely works, and sending as a free Gmail or Yahoo address fails.
For an Indian small business: Zoho Mail for mailboxes plus a transactional service for the site.
Flow: the WordPress form sends through an SMTP plugin on port 587 to an authenticated relay, which signs the message so SPF and DKIM pass and it reaches the inbox.
Stop letting the web server send mail. Let a real mail server do it.
wp-config.php constants for WP Mail SMTP: SMTP mailer, relay host, port 587 and TLS highlighted, authentication on, and the forms@yourdomain.in username and app password.
Constants keep the credential out of database exports you hand to a developer.

Stop letting the web server send mail. Instead, have WordPress connect to a proper mail server with a username and password, the same way your phone’s mail app does. That server already has a reputation, correct records and retry logic. Install one of the SMTP plugins, pick your provider, and put the credentials in.

Two categories of relay are worth considering. A transactional email service is built for exactly this and gives you logs, retries and a dashboard. Alternatively, if your domain’s mailbox is on a paid plan with Google Workspace, Zoho Mail or a similar provider that allows SMTP access, you can usually send through it with an app password, which is plenty for a form that fires a few times a day. Free mailbox tiers often leave SMTP out (Zoho’s Forever Free plan, for example, excludes IMAP and POP), so check before you rely on one.

RouteGood forWatch out for
Transactional email serviceForms, orders, password resetsSender verification and a free-tier send limit
Your mailbox provider over SMTPLow-volume small business sitesDaily sending caps, app password required
Hosting server’s local mailAlmost nothingNo authentication, no logs, no delivery
Sending as a free Gmail or Yahoo addressNothing at allFails authentication; Yahoo’s DMARC policy says reject, and Google warns against it

For an Indian small business I usually suggest Zoho Mail for the mailboxes, since billing and support are local, plus a transactional service for anything the site itself sends. Both have free or low tiers that cost a few hundred rupees a month at most for this kind of volume, and the separation means a busy form cannot affect your actual inbox. Some hosts bundle mail with the hosting plan, which is convenient and also the part of the arrangement most likely to become a problem later; I say a bit more about that in my notes on web hosting for Indian sites.

If your SMTP plugin supports it, store the password as a constant in wp-config.php rather than in the plugin’s database settings. It keeps the credential out of database exports you might hand to a developer, and it means a compromised admin account does not reveal it in a settings screen.

/* in wp-config.php, above the "stop editing" line.
   These names are for the WP Mail SMTP plugin; other SMTP plugins
   use their own constant names, so check that plugin's documentation. */
define( 'WPMS_ON', true );
define( 'WPMS_MAILER', 'smtp' );
define( 'WPMS_SMTP_HOST', 'smtp.example-relay.com' );
define( 'WPMS_SMTP_PORT', 587 );
define( 'WPMS_SSL', 'tls' );
define( 'WPMS_SMTP_AUTH', true );
define( 'WPMS_SMTP_USER', 'forms@yourdomain.in' );
define( 'WPMS_SMTP_PASS', 'the-app-password-goes-here' );

the From address is not a detail

Comparison: using the visitor's Gmail address as From fails authentication; sending from forms@yourdomain.in with the visitor in Reply-To passes and keeps easy replies.
Convenience in the From field is what breaks delivery.

The single most common error in this whole area: setting the form’s From address to the visitor’s own email so that replying is convenient. That asks your server to send mail as, say, a Gmail user, which it cannot authenticate. Google’s sender guidelines tell senders not to impersonate Gmail From: addresses and warn that it hurts delivery, and Yahoo publishes a DMARC policy telling receivers to reject such mail outright. The form then fails for exactly the people most likely to contact you.

Send from an address on your own domain, something like forms at yourdomain, and put the visitor’s address in the Reply-To header instead. You keep one-click replies and the message passes authentication. While you are in there, make sure the domain in that address is the one you actually control; my domain name guide covers the registrar and DNS split that confuses this for a lot of people.

the DNS records to publish

DNS records: a single SPF TXT record including two senders, a DKIM TXT record at sel1._domainkey, and a DMARC record with p=none and a reporting address.
Move DMARC from p=none to quarantine only after the reports look clean.

Your relay provider will give you the exact values. The shapes look like this, and the important discipline is one SPF record, not three.

# SPF: one TXT record at the root, listing every sender
@    TXT    "v=spf1 include:_spf.google.com include:relay.example.com ~all"

# DKIM: the selector and key your provider hands you
sel1._domainkey    TXT    "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."

# DMARC: start by only asking for reports
_dmarc    TXT    "v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.in"

Two failure modes to avoid. Publishing multiple SPF TXT records invalidates SPF entirely rather than merging them, so combine every sender into a single record. And jumping straight to a rejecting DMARC policy before you know all your senders will bounce your own newsletter, your invoicing tool and possibly your accountant. Monitoring mode first, always.

testing, properly

Five testing steps: submit the real form, send to Gmail, Outlook and your own domain, read the authentication headers, check spam folders, and retest after changes.
The headers are the evidence. Anything else is an opinion about your email.

Sending yourself a test and seeing it arrive proves very little, because your own provider trusts you more than a stranger’s does. Do this instead:

  1. Submit the real form, not the plugin’s test button, so you exercise the same code path a visitor does.
  2. Send to three different providers: a Gmail address, an Outlook address, and one mailbox on your own domain.
  3. In Gmail, open the message, choose Show original, and read the SPF, DKIM and DMARC lines. All three should say pass.
  4. Check the spam folder as well as the inbox. Delivered to spam is a different problem from not delivered.
  5. Repeat after any change to DNS, hosting or the plugin.

The headers are the evidence. Anything else is an opinion about your email.

log the sends, and store the submission

Checklist: enable the SMTP email log, save every form submission in the database, and check the log occasionally.
The difference between a small annoyance and nine lost weeks.

Even with everything configured, mail fails sometimes. A relay has an outage, a card expires, a monthly limit is hit. The difference between a small annoyance and nine lost weeks is whether anything noticed.

So do two things. Turn on the email log in your SMTP plugin, which records each attempt and its result, and check it occasionally. More importantly, use a form plugin that saves every submission to the database. Then a mail failure costs you a notification, not the enquiry itself. If your site takes money as well as messages, the same reasoning applies to order confirmations, which I get into in taking payments on a small Indian website.

common mistakes

  • Using a free Gmail or Yahoo address as the From address. Your server cannot authenticate it, Yahoo’s published DMARC policy tells receivers to reject it, and Google’s sender guidelines warn against impersonating Gmail.
  • Skipping DKIM because SPF was easier. DKIM is what survives forwarding and what DMARC usually leans on.

FAQ

Can I just use my hosting provider’s mail server?

You can, and it is better than local mail because you at least authenticate. The catch is reputation: shared hosting IPs are used by hundreds of sites, and one bad neighbour affects everyone’s delivery. For a form that sends a handful of messages a day it usually works. For anything you would be upset to lose, use a dedicated relay.

Port 587 or 465?

Use 587 with STARTTLS as the default, and 465 with implicit TLS if your provider recommends it. Both are encrypted; the difference is when encryption starts. Some Indian hosts block outbound 25 entirely to limit junk mail, which is another reason the unauthenticated local-mail route often fails silently.

My mail goes to spam even though everything passes. Now what?

Authentication proves identity, not welcome. Look at content and consistency next: a subject line full of capitals, a message that is one big image, a From name that changes every week, or a brand-new sending domain with no history. Send a low volume of plain, predictable mail from one consistent address for a few weeks and it generally settles.

Do I need this if I only use WhatsApp for enquiries?

Yes, though less urgently. WordPress still sends password resets, update notices and plugin alerts, and those failing silently is how people get locked out of their own admin panel. It is also part of the launch sweep for new sites, alongside the items in my technical SEO checklist.

Email is the one part of a small website that fails without any visible symptom, which is why it stays broken for months. Fix the sending path, publish the three records, then go and test with a real form submission today.

Update history: corrected what Gmail’s and Yahoo’s DMARC policies say, replaced the wp-config.php constants with the names an SMTP plugin actually reads, and cited the current DMARC standard.

Sources