send.webforms.toRead the docs →

how-toemaildebugging

Contact form not sending email? Find the broken layer before you fix anything

"The form isn't sending" describes four different failures. Work out which hand-off broke first, because each fix only repairs its own layer.

WebForms 7 min read

"The contact form isn't sending" is a report about the very end of a pipeline with four hand-offs, and it sounds the same whichever one broke. That is why the usual fixes are hit and miss. An SMTP plugin repairs one layer; if the submission never reached the server, it changes nothing, and now there is one more thing to debug. Work from the start of the pipeline and stop at the first layer that fails.

Four hand-offs, four questions

  1. Did the submission arrive? The browser sent the form, and the thing on the other end received it and kept it.
  2. Did the mail leave? Your site or service handed a message to a mail server.
  3. Was it accepted? The recipient's mail server took the message instead of refusing it.
  4. Where did it land? The inbox, the junk folder, a filter rule, or a quarantine you never see.

Layer 1: did the submission arrive?

Submit the form yourself with the browser's developer tools open on the Network tab, and find the request the form makes. No request at all means something stopped it before it left: a JavaScript error, a captcha that never loaded, a validation rule. The Console tab usually names it. A request answered with a 4xx or 5xx status means the server refused it, and the response body normally says why. A 200 or a redirect means the server accepted the request, which raises the next question: did it keep it?

That is the catch with a lot of form tools. If the email is the only copy of a submission, layers 1 and 2 look identical from outside, because a submission that arrived and then failed to send leaves no trace anywhere. If your platform can store submissions, switch that on before you debug anything else. Then every later test tells you something.

Layer 2: did the mail leave?

On WordPress, Contact Form 7, Elementor and Divi all send through wp_mail(), which by default hands the message to PHP's mail() function. WordPress keeps no log of mail it sends, and its own reference for wp_mail() warns that a true return value does not automatically mean the email was received. What it does have is a hook, wp_mail_failed, which fires when the mailer reports an error. Log it and the reason stops being a guess:

wp-content/mu-plugins/log-mail-failures.php
<?php
add_action('wp_mail_failed', function (WP_Error $error) {
    error_log('wp_mail failed: ' . $error->get_error_message());
});

The usual answers at this layer are a host that has disabled mail() or caps how much it sends, an SMTP plugin with stale credentials, or a hosting plan that blocks outgoing SMTP connections altogether. Elementor's and Divi's own troubleshooting pages both name the first one: hosting providers can switch off the mail function their forms depend on.

Layer 3: was it accepted?

A message can leave your server and still be refused at the other end. The refusal comes back as a bounce, addressed to the envelope sender, which on shared hosting is often a server mailbox nobody reads, so the site owner never hears about it. The common reasons are a typo in the recipient address, a mailbox that no longer exists, and DMARC: if the form sends From the visitor's address, the recipient's server may refuse it on the visitor's email provider's instructions. That last one produces the most baffling pattern of all, where enquiries from some visitors arrive and others never do. The companion post on spam covers it in full.

If you send through an SMTP or email API provider, its activity log is the evidence for this layer: it shows what was accepted and what bounced, with the receiving server's reason.

Layer 4: where did it land?

Accepted is not the same as in the inbox. Check junk, then inbox rules, then any tabs or secondary views the mail client sorts messages into. In an organisation, a mail gateway can hold messages in a quarantine that never reaches anyone's junk folder, and only whoever runs your mail can search it. Nobody on the sending side can see this layer, because every sender's records stop at accepted.

Platform notes

Contact Form 7

Contact Form 7 does not store submissions; its documentation says so and points to Flamingo, a separate plugin by the same author. Install it first, so layer 1 has a record. Then check the Mail tab: the plugin's configuration validator reports an error when the From address is not on your site's domain, and its own best-practice advice is a From address on the site's domain with the visitor in a Reply-To header.

Elementor

Elementor Pro collects submissions by default under Elementor, then Submissions, and its troubleshooting page suggests using them to confirm the form works. If the submission is there and the email is not, the problem is layer 2 or later. Elementor's advice is a From address on your own domain, the visitor's address in Reply-To, and an SMTP connection if the host has disabled PHP mail.

Divi

Divi's Contact Form module sends through wp_mail(). If its Email Address field is left empty, messages go to the email address used when WordPress was installed, which is often an address nobody checks any more. Elegant Themes recommends an SMTP plugin or a delivery service when mail goes missing.

Wix

Wix stores every submission under Forms & Submissions in the dashboard, so layer 1 is easy to confirm. Notifications are built with Wix Automations and go to the site owner by default; anyone who is not a collaborator has to be added as a contact before an automation can email them, and changing your Wix account email does not update the recipient in existing form automations. For missing emails, Wix's own troubleshooting says to check spam for its notification senders, allow its sending domains, and republish the site. It also notes that some providers treat a message as spam when the sender and recipient addresses are the same.

Squarespace

Each Squarespace form block emails one address, by default the account email of whoever added the block, and it does not follow you if you later change your account email. Form submission emails are disabled entirely on trial sites. Submissions are also stored on the site (until you delete them when the form has a required email field, for 30 days when it does not), and notifications come from form-submission@squarespace.info, which Squarespace suggests allowing with a filter.

GoDaddy

On GoDaddy's website builder, the contact section sends to the address in its Send Message To field, or to the primary address on your GoDaddy account if that is blank, and that address is separate from the site's public contact email. Messages are also kept in the GoDaddy Conversations inbox. On GoDaddy hosting, the help centre recommends PHP mail() for scripts and, on Managed WordPress, says connections on ports 25, 465 and 587 are blocked, so an SMTP plugin pointed at an outside mail server cannot connect there.

How WebForms makes each layer visible

We built WebForms so that every one of those questions except the last has an answer in the dashboard, and so that the answer does not depend on the email arriving.

  • Layer 1: every accepted submission is stored before any delivery is attempted, so the form's inbox is proof it arrived. A refusal comes back with a stable error code, such as quota_exceeded (402), account_unverified (403), form_not_found (404), validation_failed (422) or rate_limited (429); a plain HTML form with a _redirect field gets a validation failure back as wf_error on that URL instead. Submissions the spam filter quarantines are stored in the Spam view and never delivered. The exception is a submission rejected outright, such as a filled honeypot or a failed captcha: it is not stored, and the sender sees a normal success response on purpose, so a bot learns nothing.
  • Layers 2 and 3: each form's Deliveries tab has a row for every destination of every submission, with its status, attempts, error and duration. A failed email shows the reason, and the email destination itself says why an address has stopped receiving mail.
  • Layer 4 stays invisible to every sender, us included. If the log shows success and nothing is flagged, the receiving server accepted the message and filed it somewhere, so check junk and inbox rules before anything else.

The trade-off: one failure is final

Email is the one destination we never retry. Each notification is attempted once, and if anything goes wrong, from a refused address to a request that timed out, that email is not sent again. When the recipient's server refuses the address, or the recipient reports the message as spam, the address goes on a platform-wide suppression list and nothing more is sent to it: no form notifications, no auto-replies, no account email. Hard bounces stay until the address is released; soft bounces, such as a full mailbox, lapse after 24 hours. A whole domain is only suppressed when the domain itself cannot receive mail or three of its addresses bounce within a week, and shared providers such as Gmail are only ever blocked one address at a time.

The reason is that our sending reputation is shared by every customer. Mailbox providers penalise senders that keep mailing addresses which refused them, and a retry loop does exactly that. The cost falls on you, and it is real: a notification that fails is gone as an email. The submission is still stored and readable in the dashboard, and the destination tells you why, but nothing reaches that inbox until the address is fixed. Once the mailbox works again, email help@webforms.to and we release it. On paid plans, a second destination such as Slack or a webhook gives you a channel that does retry with backoff; on Free, the dashboard is the backstop.

Get your endpoint

Free for 300 submissions a month. No card required.

Start free