northstage

← All guides

Guide

Your contact form says “sent”, but nothing arrives

Find the point where your messages get lost and fix it, one check at a time. The first checks need no developer.

Last updated:

The short answer

The thank-you message only means the website accepted the form, not that an email was sent, delivered or read. A message gets lost in one of four places: the website never tried to send it, the mail server refused it, the receiving mailbox filtered it, or one of your own spam rules dropped it. Send one marked test and check each stage in order. A very common fix is to send from an address on your own domain with SPF and DKIM set up, and to put the visitor’s address in Reply-To.

Watch it in under a minute

A short video of the problem and its fix. Captions are in the video.

Who this is for

You run a website with a contact or enquiry form. A visitor fills it in and sees a thank-you message, but the email never shows up in your inbox. Or you suspect it happens to some visitors, because someone told you “I wrote to you last week” and you never saw it.

This guide works for WordPress, site builders and hand-built or static sites. The first checks need no technical background. Later steps involve DNS settings and the mail or hosting account, so you may need whoever looks after your domain.

How a message travels, and where it gets lost

A form submission passes through several stages, and the thank-you message usually appears after the first one:

  • The browser sends the form to a handler: a plugin, a script on your server, a serverless function or an outside form service.
  • The handler hands the message to a mail sender: your server’s built-in mail, an SMTP account or an email-sending service.
  • The sender passes it to the receiving mail server, which accepts, delays, rejects or quarantines it.
  • The message ends up in a mailbox: the inbox, the spam folder or, with forwarding rules, somewhere else.

The thank-you page only proves that the first stage worked. Everything after it can fail without you or the visitor seeing anything. That is why the fix is to check each stage in turn instead of guessing.

How to tell which cause applies

Run the test in step 1 first, then match what you see:

  • The test lands in spam or junk. The receiving server does not trust the sender. Likely cause: the From address or missing email authentication. Go to steps 3 and 4.
  • The test never arrives, and your email-sending service shows nothing for it. The handler never sent it. Likely cause: an error the handler hides, or sending that is switched off. Go to step 2.
  • The sending service shows “bounced”, “rejected” or “deferred”. Read the reason text, it usually names the problem. Go to steps 3, 4 and 7.
  • The service shows “delivered”, but you cannot find the message. The receiving server accepted it. Look at spam, quarantine, forwarding rules and the recipient address. Go to step 5.
  • It works for you, but some visitors’ messages go missing. A spam rule on your form blocks real people, or a limit is reached. Go to steps 6 and 7.
  • It worked for months and then stopped. Something changed: an expired key, a used-up quota, new DNS settings or a plugin update. Go to step 7.

Steps to fix it

  1. Run a real test and write down the time. Submit the live form yourself. Enter a different email address from the one that receives the messages, ideally at another provider, as the visitor’s address, and put a unique word in the message, such as “test-1017”. Note the time and your time zone, then wait ten minutes, because some servers delay unknown senders on purpose. Search the receiving mailbox, spam folder included, for your unique word.

  2. Find out what the handler does after “submit”. Open the page, open your browser’s developer tools (F12), go to the Network tab and submit the form. Click the request that was sent and read the answer. A “200 OK” only says that the handler replied. Then look for a record on the other side:

    • WordPress: install an SMTP plugin with a test-email function (for example WP Mail SMTP) and use a form plugin that keeps every entry, such as Contact Form 7 with the Flamingo add-on, or Gravity Forms. If the entry is stored but no email left, the sending part is broken.
    • Static or hand-built sites: read the log of the function or script that handles the form. A common bug is code that calls the email service, ignores the answer and returns “success” anyway. The handler should report success only when the email service has accepted the message, and an error otherwise.
    • Outside form service: sign in and look at its list of submissions and its delivery notes, and check which notification address is set there.
  3. Send from your own domain and put the visitor in Reply-To. Many forms use the visitor’s address as the From address. A message that claims to come from a gmail.com address but is sent by your server fails the checks that mailbox providers run (SPF, DKIM and DMARC alignment, explained in the dmarc.org FAQ), so it is rejected or filed as spam. Change the form so the message is sent from an address on your own domain, for example forms@yourdomain.com, and the visitor’s address goes into the Reply-To header. You can still press “reply” and answer the visitor.

  4. Send through an authenticated service and set up SPF, DKIM and DMARC. The mail() function in PHP, or the default mail of a web host, sends messages that are not properly authenticated, and many providers treat them with suspicion. Use the SMTP details of your mailbox provider or an email-sending service, and add the DNS records they ask for. Google’s sender guidelines say that every sender needs SPF or DKIM, and that senders of large volumes need SPF, DKIM and DMARC with an aligned From domain. A contact form sends little mail, but the same checks decide what happens to it. The records look like this (your service gives you the exact values):

    • SPF: a TXT record on yourdomain.com with a value like v=spf1 include:spf.mail-service.example ~all
    • DKIM: a TXT or CNAME record named selector1._domainkey.yourdomain.com, with the value copied from your mail service
    • DMARC: a TXT record named _dmarc.yourdomain.com with the value v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com

    A domain should have only one SPF record, so merge the include: entries into it. Use the service’s “verify domain” button, then repeat the test from step 1. Open the received message, choose “Show original” (Gmail) or view the headers, and look for spf=pass, dkim=pass and dmarc=pass.

  5. Check where the message stopped. Two places keep a record:

    • Sending side: the log of your email-sending service or SMTP provider. Find your test by time and recipient and read its status: accepted, delivered, deferred, bounced or complained. A bounce comes with a reason text. Most services show these statuses in a dashboard, for example in Resend’s list of event types.
    • Receiving side: the spam, junk and quarantine folders, mailbox rules and forwarding, and a typo in the recipient address. If your mail runs on Microsoft 365 or Google Workspace, an administrator can run a message trace (Microsoft) or an email log search (Google) to see whether the message arrived and what happened to it. If the address forwards to another mailbox, check that final mailbox too.
  6. Rule out your own spam protection. Honeypot fields, CAPTCHAs, rate limits, minimum fill-in times and word filters all reject some real people. Try the form from a phone on mobile data and with the browser’s autofill switched on. Autofill can fill a hidden honeypot field, and a mobile network or an office often shares one IP address, which reaches a rate limit sooner. A blocked submission should be stored with the reason, and the visitor should see a clear message, never a fake “sent”.

  7. Check limits, keys and recent changes. Free plans of email services and form services have daily or monthly caps, and beyond them messages are refused or dropped. Look at the usage page of your plan (Resend’s quotas are one example). Then ask what changed around the time the messages stopped: an API key that expired or was replaced, a website move or changed DNS (moving to a new host often drops mail records), a plugin or PHP update, a changed recipient address. Put expiry dates of keys and domains in your calendar.

  8. Make failures visible next time. Fixing it once is not enough, because the same thing can break again unseen. Four habits keep that from happening:

    • Keep a copy. Store every submission (a database, a spreadsheet or the form plugin’s entries) before you try to send the email, with a status such as received, sent or failed. Then no enquiry is lost even when email fails. Keep the copies access-controlled and delete them after a set time.
    • Answer honestly. Show the thank-you message only after the sender has accepted the email. Otherwise show an error with another way to reach you, such as your email address.
    • Alert on failure. Let the handler tell you when sending fails or a limit is close, through a different channel than the form’s own mailbox, for example a chat message or a second address. If your email service offers webhooks for bounces and complaints, use them.
    • Run a scheduled test. Once a day or once a week, submit a clearly marked test through the live form and check that it was stored, accepted and delivered. If it does not arrive in the expected time, raise the alert. A scheduled job in your CI system or an uptime service that can run a form check will do.

    For the logs, record the UTC time, a submission ID, the email service’s message ID, the status and any error code. Leave the message text, passwords and keys out of them, as the OWASP logging cheat sheet advises.

When to get help

Bring in someone experienced if:

  • the test still goes missing after steps 1 to 5, although the service says “delivered”
  • you cannot reach the DNS settings, the hosting account or the mail administration, or do not know who can
  • the form is custom code that nobody maintains
  • bounce or DMARC reports show senders you do not recognise using your domain
  • lost messages would cost you customers or deadlines, as with quote requests, bookings or legal notices, and you want a monitored setup instead of hoping

Want a hand with it?

You can do all of this yourself. If you would rather hand it over, book the free 30-minute video call and bring the address of the page with the form and what you saw in your test. Or just send a message.