Why Contact Form Emails Don't Arrive, and How to Fix It
· Go Komura · Email Delivery, SPF, DKIM, DMARC, Inquiry Flow, B2B
Executive Summary
The biggest reason contact-form notification emails “send successfully but never arrive” is not the SMTP connection itself — it is that the visible sender (From:) and the sender that actually gets authenticated (SPF’s MAIL FROM / DKIM’s d=) do not line up. DMARC takes RFC5322.From as its reference and checks whether either SPF or DKIM passes with alignment. Which means a design that puts the form submitter’s Gmail or company address directly into From: — however convenient it looks for replying — is a design that fails easily in real operation.
The practical conclusion is simple. Pin the From: of inquiry notification emails to your own site’s domain, and put the form user’s address in Reply-To:. Use Sender: only when “the author and the actual sending agent differ”, and set Return-Path not by hand-writing the header, but as the envelope sender at the MTA or mail-service level.
Also, forwarding easily breaks SPF, and when mailing lists or relay servers rewrite the body or headers, DKIM breaks too. That is exactly why, for contact forms, the safe approach is to never rely on SPF alone — always enable DKIM — and start DMARC at p=none for observation before advancing to quarantine / reject.
Furthermore, for mail sent to Gmail, Google requires the sending domain to have SPF or DKIM configured, requires senders above a certain volume to have SPF, DKIM, and DMARC all in place, and requires the domain in the From: header to match the SPF- or DKIM-authenticated domain. The true identity of delivery problems is usually not “the mail code” but “the sender design”.
The Roles of SPF, DKIM, DMARC, and the From Header
In mail delivery, you must think of the envelope and the headers separately. What SPF primarily validates is the MAIL FROM during the SMTP session — the delivery-level sender. At final delivery, exactly one copy of that return path should be recorded as Return-Path, and sending SMTP systems should not construct messages that carry a Return-Path header from the start. Meanwhile, From: expresses, on the message-header side, “who the mail appears to be from”; Reply-To: expresses the reply target; and Sender: expresses the agent that actually transmitted it.
RFC 5322 defines From: as the author of the message and Sender: as the actual transmitting agent. If the author and the transmitting agent are the same, Sender: should not be used; if you want the reply target to differ from the author, the correct tool is Reply-To:. RFC 5322 further states explicitly that From: should not contain an address that does not belong to the author. A contact-form notification is normally not sent by the user from their own MUA — it is a notification constructed by the site’s system — so a design that places the user’s address in From: also drifts from the meaning of the specification.
DKIM attaches a signature over a subset of the headers and the body, and the receiver verifies it against a public key in DNS. The signing domain appears as d= in the DKIM-Signature, and the public key is looked up via a selector, as in selector._domainkey.example.com. DKIM is used as the authentication that “survives forwarding comparatively well”, but if the body or the signed headers are modified in transit, the bh= body hash or the signature verification fails.
DMARC looks not merely at whether SPF and DKIM passed, but at whether their authenticated domains align with the From: domain. This is the crux. Even if SPF passes, with MAIL FROM=bounces.vendor.net and From: contact@example.com, SPF alignment fails. If DKIM passes with d=example.com, DMARC can still pass — but with no DKIM either, DMARC fails. DMARC defines strict / relaxed alignment modes via adkim / aspf, the policies p=none|quarantine|reject, and the report destinations rua / ruf.
Gmail’s recent guidelines translate this design philosophy directly into operational requirements. Google explicitly states “do not spoof the From: header” and “for direct mail, the domain in the From: header must match the SPF domain or the DKIM domain”. A contact-form notification is not marketing mail, but the basic logic of receiving-side filters is the same, so ignoring this thinking lowers your delivery rate.
sequenceDiagram
participant User as Form user
participant App as Web app
participant SMTP as Sending MTA / SMTP service
participant DNS as DNS
participant MX as Receiving MX
User->>App: Submit the form
App->>App: Decide From / Reply-To / Sender
App->>SMTP: SMTP send request
SMTP->>SMTP: Apply DKIM signature
SMTP->>MX: MAIL FROM / RCPT TO / DATA
MX->>DNS: SPF lookup (MAIL FROM)
MX->>DNS: DKIM public key lookup (selector._domainkey)
MX->>DNS: DMARC lookup (_dmarc + From domain)
MX->>MX: Alignment evaluation
MX-->>App: Delivered / spam / rejected / bounced
What matters in this flow is that DMARC’s evaluation remains centered on the From: domain to the very end. Because the app touches From:, the SMTP side touches MAIL FROM, and the DNS side touches SPF/DKIM/DMARC — each separately — fixing just one of them does not resolve non-delivery.
Failure Scenarios Common to Contact Forms
The most common is putting the form user’s address into From:. For example, if you send from the site’s SMTP but set From: taro@gmail.com, what passes SPF or DKIM is normally the site-side domain, not the Gmail-side domain. The result is the misalignment of From: being Gmail while the authenticated domain is example.com — and DMARC alignment fails. Google itself demands avoiding From: spoofing and matching From: with the SPF/DKIM domains.
The next most common is SPF breaking on forwarding. With mail forwarding, the source IP as seen by the final recipient tends to be “a relay server not listed in the original sending domain’s SPF”, so even legitimate mail fails SPF. Google likewise advises that “forwarded mail easily fails SPF, so always use DKIM”. On top of that, if the relay adds a subject prefix or a footer or otherwise touches the body, DKIM breaks too.
Incomplete initial setup of external SMTP services is also typical. With SendGrid, there are cases where you cannot send until Domain Authentication is configured; turning Automated Security ON generates CNAME-based authentication records, and you can configure a Custom Return Path or a Custom DKIM Selector as needed. With SES, the default MAIL FROM uses amazonses.com, so SPF passes implicitly — but if you want SPF alignment with your site’s domain, you must configure a custom MAIL FROM. With Mailgun too, until the sending domain’s SPF/DKIM and the required MX records are in place, correct sending signatures do not materialize.
The local MTA on shared hosting is an easily overlooked landmine. Even if your site is authenticated, if the IP that actually leaves is a shared IP with a poor reputation, has no PTR, or the hosting does not apply DKIM, delivery suffers. Google has made the sending IP’s PTR a requirement, and notes that a poor shared-IP reputation can cause 5.7.1-class errors.
How PHP’s mail() or a sending library is used is another frequent failure point. The PHP manual explains that mail() requires a From header, and that the additional-parameters argument lets you specify the envelope sender via sendmail -f. In other words, the right move is handing the envelope sender to the MTA, not assembling a Return-Path: header yourself. Misunderstand this, and the sender used for SPF evaluation diverges from the sender your application assumes.
Finally, there is the case of fiddling with Sender or From where Reply-To should be used. If all you want is to direct replies to the user, Reply-To is sufficient. Sender is the header for explicitly stating that “the author and the actual transmitting agent differ” — not something to use routinely in a contact form. Separating the design goal — “do we want replies to go back to the user?” versus “do we want to indicate the responsible sending agent?” — prevents accidents.
Diagnostic Procedures and Commands
The first thing to do is look at the raw mail headers before looking at the code. In Gmail, use “Show original”; in Outlook, use “Message details” or “Internet headers” — and check Authentication-Results, Return-Path, From, Reply-To, DKIM-Signature, and Received. From these you can determine, with considerable precision, whether the problem is “not sending” or “authentication and alignment have broken down”.
What to Look at First in the Headers
The top five priorities:
- What domain is in
From: - What domain is in
Return-Path: - Does
Authentication-Results:showspf=pass/dkim=pass/dmarc=pass - When
dkim=pass, which domain isheader.i=ord= - When
dmarc=fail, is the reason an authentication failure or an alignment failure
Google’s DMARC troubleshooting also states explicitly that a message can pass the other authentications and still fail DMARC if the headers are not aligned.
Commands for Checking DNS
dig is the standard tool for DNS troubleshooting — the BIND manual describes it as a DNS lookup tool with flexible, clear output. nslookup is a lighter checking command that also works in non-interactive mode. For contact-form authentication checks, query at least these three: SPF, DKIM, and DMARC.
# SPF
dig +short TXT example.com
# DKIM
dig +short TXT form2026._domainkey.example.com
# DMARC
dig +short TXT _dmarc.example.com
# On Windows
nslookup -type=txt example.com
nslookup -type=txt _dmarc.example.com
nslookup -type=txt form2026._domainkey.example.com
Expected output looks like this:
"v=spf1 include:sendgrid.net include:mailgun.org ip4:203.0.113.10 -all"
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
"v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com; ruf=mailto:dmarc-afrf@example.com; adkim=r; aspf=r; fo=1"
What to look for here: is the SPF record consolidated into one, can the DKIM public key be resolved, and does DMARC have a p=. RFC 7208 mandates that SPF should be limited to a total of 10 mechanisms that trigger DNS lookups; exceeding that causes permerror.
Checking the SMTP Connection and TLS
openssl s_client is OpenSSL’s general-purpose SSL/TLS client, handy for checking an SMTP server’s STARTTLS and certificate chain. -starttls smtp initiates SMTP STARTTLS, and -showcerts shows the certificates the server returned.
printf 'QUIT\r\n' | openssl s_client \
-connect smtp.example.com:587 \
-starttls smtp \
-servername smtp.example.com \
-showcerts \
-brief
Example output:
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Verification: OK
250 CHUNKING
With this you can at least see whether the SMTP server is reachable, whether STARTTLS is enabled, and whether certificate verification shows any obvious anomaly. Google requires TLS of senders above a certain volume, and missing TLS can cause 5.7.29.
Reproduction Test with a Real Send
swaks is a practical tool dedicated to SMTP testing; it flexibly reproduces sends including TLS, authentication, and SMTP extensions. For contact-form non-delivery investigations, it is effective to send exactly one message “bypassing the app — same SMTP, same From, same Reply-To, same recipient” and reproduce the issue.
swaks \
--server smtp.example.com \
--port 587 \
--tls \
--auth LOGIN \
--auth-user contact@example.com \
--auth-password '********' \
--from bounce@example.com \
--to yourtest@gmail.com \
--h-From "Site Notifications <contact@example.com>" \
--h-Reply-To "Taro Yamada <visitor@gmail.com>" \
--header "Subject: swaks test" \
--body "This is a test"
A typical successful send looks like:
=== Trying smtp.example.com:587...
=== Connected to smtp.example.com.
<- 250-STARTTLS
<- 250-AUTH LOGIN PLAIN
-> STARTTLS
<- 220 Ready to start TLS
...
<- 250 2.0.0 Ok: queued as ABC123DEF
If it fails via the app but passes with swaks, the cause most likely lies in the library’s header assembly or the envelope-sender configuration. Conversely, if it fails the same way with swaks, you can narrow it down to DNS, SMTP, or receiving-side policy.
External Diagnosis with mail-tester
mail-tester is a service that has you send mail to a randomly issued test address, analyzes the message, the sending server, and the sending IP, and returns a detailed report. It is well suited as a first-pass diagnosis for the “it just somehow doesn’t arrive” cases on local MTAs and shared servers.
Usage is simple:
- Obtain the test address issued by mail-tester
- Send one message through the same path as the contact form
- Review the score and the findings on SPF / DKIM / DMARC / reverse DNS / blacklists / body composition
Do not judge production deliverability by the mail-tester score alone — but at minimum, things like “SPF is not visible at all”, “the DKIM public key cannot be resolved”, or “the body or sender composition is unnatural” surface quickly.
Header Analysis Samples
A failing example:
Return-Path: <bounce-123@vendor.example.net>
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of bounce-123@vendor.example.net designates 198.51.100.10 as permitted sender) smtp.mailfrom=vendor.example.net;
dkim=none;
dmarc=fail (p=quarantine sp=quarantine dis=none) header.from=gmail.com
From: Taro Yamada <visitor@gmail.com>
Reply-To: Taro Yamada <visitor@gmail.com>
Subject: Inquiry
This mail passes SPF itself, yet fails DMARC because From: is gmail.com. It is the classic way contact forms break when the user’s address is put into From:.
A successful example:
Return-Path: <bounce@example.com>
Authentication-Results: mx.google.com;
spf=pass smtp.mailfrom=example.com;
dkim=pass header.i=@example.com header.s=form2026;
dmarc=pass header.from=example.com
From: Example Site <contact@example.com>
Reply-To: Taro Yamada <visitor@gmail.com>
Subject: Inquiry notification
In this shape, ease of replying to the user is preserved via Reply-To:, while both the visible sender and the authenticated sender line up on example.com — and deliverability stabilizes dramatically.
Recommended From Design Patterns
The principle that never wavers for contact forms: align “the domain used for authentication” with “the From domain shown to recipients”. Then route only the reply target out through Reply-To:. Split the design into three layers — Sender: only when needed, Return-Path set via the envelope — and it stays stable.
| Pattern | Header example | Suited to | Strengths | Caution |
|---|---|---|---|---|
| Recommended pattern | From: contact@example.comReply-To: visitor@gmail.comReturn-Path: bounce@example.com |
Nearly every contact form | Easy to pass DMARC / easy to reply / simple to implement | Forget Reply-To and replies go to the site side |
| Subdomain separation | From: contact@form.example.comReply-To: visitor@gmail.comReturn-Path: bounce.form.example.com |
Separating form notifications from primary mail | Easy reputation separation / easy to manage | SPF/DKIM/DMARC must also be set up on the subdomain side |
Explicit Sender |
From: contact@example.comSender: mailer@example.comReply-To: visitor@gmail.com |
Special requirements to declare the sending agent | Shows the operationally responsible agent | Normally unnecessary. Redundant if author and sender are the same |
| Anti-pattern | From: visitor@gmail.comReply-To: visitor@gmail.com |
Implementations that merely want the reply target prominent | Only looks natural | A frequent cause of DMARC failure. Avoid for inquiry notifications |
The grounds for this table are RFC 5322’s semantics for From / Sender / Reply-To, RFC 5321’s handling of Return-Path, and the specification that DMARC evaluates alignment against From:. For form notifications, the “recommended pattern” in row one is sufficient as the default. Even when you are tempted to put the user’s address in From:, putting the reply target in Reply-To: achieves the goal.
The thing to remember above all: Return-Path is not “a header you edit” but “the result of the envelope sender used in delivery”. With PHP mail() that means -f; with an SMTP service it means configuration items like Custom MAIL FROM / Return Path / bounce domain. That is the correct implementation.
Configuration Guide by Setup
From here, we organize the configuration thinking for the three setups most common in the field. As a premise, the exact values to put in DNS should come from each service’s admin console. The record examples below are representative, for understanding the structure.
When the Site Uses an External SMTP Service
The single most important point with external SMTP is completing your own domain’s authentication first. Google likewise advises that, when using an email service provider, you confirm the service authenticates your domain’s SPF and DKIM.
Recommended configuration
From:iscontact@example.comorcontact@form.example.comReply-To:is the form user’s addressReturn-Path/ MAIL FROM is a bounce subdomain you control, e.g.bounce.example.com- DKIM signs with
example.comor the sending subdomain - DMARC sits on the visible
From:domain
Typical SendGrid setup
SendGrid presumes Domain Authentication; with Automated Security ON, three CNAMEs are generated. With it OFF, one MX and two TXTs are generated, and you can also configure a Custom Return Path and a Custom DKIM Selector.
; Example: SendGrid (use the values generated in the admin console)
em123.example.com. CNAME u123456.wl.sendgrid.net.
s1._domainkey.example.com. CNAME s1.domainkey.u123456.wl.sendgrid.net.
s2._domainkey.example.com. CNAME s2.domainkey.u123456.wl.sendgrid.net.
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com"
The state people get stuck in with SendGrid is “SMTP authentication done, domain authentication never configured”. In that state sending itself works, but from the receiver’s perspective the relationship between From: and the authenticated domain is weak. The baseline is to complete Domain Authentication, then configure a Custom Return Path as needed.
Typical SES setup
SES uses a MAIL FROM on an amazonses.com subdomain by default, so SPF passes implicitly. But if you want SPF alignment with your site’s domain, use a custom MAIL FROM. SES then requires an SPF TXT and an MX on the custom MAIL FROM domain, and the MX must be exactly one. Easy DKIM additionally adds three CNAMEs to DNS.
; Example: SES Easy DKIM
abcde12345._domainkey.example.com. CNAME abcde12345.dkim.amazonses.com.
fghij67890._domainkey.example.com. CNAME fghij67890.dkim.amazonses.com.
klmno54321._domainkey.example.com. CNAME klmno54321.dkim.amazonses.com.
; Example: SES custom MAIL FROM
bounce.example.com. MX 10 feedback-smtp.ap-northeast-1.amazonses.com.
bounce.example.com. TXT "v=spf1 include:amazonses.com -all"
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com"
What matters in the SES design is making the MAIL FROM domain a bounce-dedicated subdomain, not the sending From: domain itself. AWS also advises that the MAIL FROM be a subdomain rather than the very domain you send mail from.
Typical Mailgun setup
When verifying a sending domain with Mailgun, you need a TXT for SPF and a TXT for DKIM, plus two MX records. If SPF already exists, do not add a new SPF record — splice include:mailgun.org into the existing one. Multiple DKIM keys may be visible, but sending works as long as the currently used key is correctly published in DNS.
; Example: Mailgun on a subdomain
mg.example.com. TXT "v=spf1 include:mailgun.org ~all"
mx._domainkey.mg.example.com. TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
mg.example.com. MX 10 mxa.mailgun.org.
mg.example.com. MX 10 mxb.mailgun.org.
email.mg.example.com. CNAME mailgun.org.
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com"
Mailgun pairs well with subdomain operation; carving out a dedicated sending subdomain like mg.example.com makes form notifications and transactional notifications easier to manage.
When the Site Sends via the Local MTA on Shared Hosting
On shared hosting, the first thing to examine is the quality of the hosting provider’s sending infrastructure, not your application. If the sending IP’s PTR, DKIM support, the shared IP’s reputation, or visibility into sending logs is weak, this setup is disadvantaged from the start. Google places weight on the sending IP’s PTR, and notes that a poor shared-IP reputation can be a cause of blocking.
Practically, this order is the safe one:
- Confirm whether the hosting company allows SPF/DKIM/PTR configuration via the control panel or support
- Always pin
From:to your own domain - Include the hosting’s sending IPs or permitted sending domain in SPF
- Enable DKIM via the hosting feature. If absent, switch to external SMTP
- If possible, separate the MAIL FROM bounce address out as
bounce.example.comor similar
As a typical example of generating DKIM yourself on shared hosting, with the OpenDKIM family you can use opendkim-genkey to generate the private key and the TXT record for DNS. The structure of publishing the DKIM public key at selector._domainkey.example.com with a selector matches RFC 6376.
mkdir -p /etc/opendkim/keys/example.com
opendkim-genkey -D /etc/opendkim/keys/example.com -d example.com -s form2026
chown opendkim:opendkim /etc/opendkim/keys/example.com/form2026.private
chmod 600 /etc/opendkim/keys/example.com/form2026.private
The image after generation:
form2026._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
# /etc/opendkim/KeyTable
form2026._domainkey.example.com example.com:form2026:/etc/opendkim/keys/example.com/form2026.private
# /etc/opendkim/SigningTable
*@example.com form2026._domainkey.example.com
That said, if you cannot control the PTR or the outbound relay yourself on shared hosting, moving to external SMTP is the shortcut. Even for low-volume sending like contact-form notifications, a weakly authenticated local MTA is at a disadvantage against Gmail and corporate mail.
When the Site Uses PHP mail() or an SMTP Library
PHP mail() is convenient, but in terms of authentication and deliverability it depends on “what the MTA behind it actually is”. The PHP manual explains that mail requires a From header, and that for sending via sendmail_path you can specify the envelope sender through the additional parameters. Put the other way around: using mail() does not automatically put SPF/DKIM/DMARC in order.
At minimum, design it like this:
From:iscontact@example.comReply-To:is the form user- The envelope sender is
bounce@example.com - Also state the user’s address in the body
- If possible, use authenticated SMTP instead of
mail()
Minimal mail() example
Putting user input straight into headers is dangerous. If CR/LF sneaks into $name or $email, an attacker can inject additional headers such as Bcc:, turning your form into a spam relay. The PHP manual likewise instructs that external input used in headers must always be validated/normalized. The example below sanitizes every value that goes into headers — including the envelope-sender argument (additional_params).
<?php
// Return only values safe for use in headers. Reject anything containing CR/LF/NUL.
function sanitize_header_value(string $value): string {
if (preg_match('/[\r\n\0]/', $value)) {
throw new InvalidArgumentException('Invalid characters in header value');
}
return trim($value);
}
// Validate the email address per RFC.
function sanitize_email(string $email): string {
$clean = sanitize_header_value($email);
if (!filter_var($clean, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email address');
}
return $clean;
}
$to = 'ops@example.com';
$subject = sanitize_header_value('Inquiry notification');
// $name / $email / $message are form inputs. $message goes in the body, so CR/LF is allowed there,
// but $name / $email are used in headers, so CR/LF must always be rejected.
$safeName = sanitize_header_value($name);
$safeEmail = sanitize_email($email);
$body = <<<TEXT
Name: {$safeName}
Email: {$safeEmail}
{$message}
TEXT;
$headers = [
'From' => 'Example Site <contact@example.com>',
'Reply-To' => sprintf('%s <%s>', $safeName, $safeEmail),
'Content-Type' => 'text/plain; charset=UTF-8',
];
// additional_params is also passed to the shell, so use fixed values only — never mix in dynamic input.
mail($to, $subject, $body, $headers, '-fbounce@example.com');
This example has two key points. One: Return-Path: is not written as a header — the envelope sender is passed via -f in the fifth argument. Two: user input that goes into headers such as Reply-To: passes through a sanitizer that rejects CR/LF. Assemble headers without sanitizing and an attacker can pour in a string like \r\nBcc: victim@example.com to inject extra headers — which is why the PHP manual treats validation of external input used in headers as mandatory. Since additional_params ultimately reaches the shell, pass fixed values only and never mix in user input.
Example using an SMTP library
<?php
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->Port = 587;
$mail->SMTPAuth = true;
$mail->SMTPSecure = 'tls';
$mail->Username = getenv('SMTP_USER');
$mail->Password = getenv('SMTP_PASS');
$mail->setFrom('contact@example.com', 'Example Site');
$mail->addAddress('ops@example.com');
$mail->addReplyTo($email, $name);
// Some libraries let you set Sender / return-path separately
$mail->Sender = 'bounce@example.com';
$mail->Subject = 'Inquiry notification';
$mail->Body = $body;
$mail->send();
The advantage of an SMTP library is easy, separate control of the header sender and the envelope sender. For contact-form use, it is the best fit for the design that pins From: to the site’s domain and places only the reply target in Reply-To:.
Concrete SPF, DKIM, and DMARC Examples
Basic SPF example
example.com. TXT "v=spf1 ip4:203.0.113.10 include:sendgrid.net -all"
SPF must include every source that actually sends. When using a third-party sender, Google likewise asks you to confirm that sender is authenticated by your SPF and DKIM. Also note that SPF has a DNS-lookup limit, so be careful about stacking up includes.
Basic DKIM example
form2026._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
Selectors exist for key rotation, allowing multiple public keys to coexist on the same domain. Operationally, names distinguishable by purpose or date are easier to read later than default.
DMARC rollout examples
First, observation mode:
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com; ruf=mailto:dmarc-afrf@example.com; adkim=r; aspf=r; fo=1"
Next, when you want to quarantine a portion:
_dmarc.example.com. TXT "v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc-agg@example.com; adkim=r; aspf=r"
Finally, strict operation:
_dmarc.example.com. TXT "v=DMARC1; p=reject; sp=reject; rua=mailto:dmarc-agg@example.com; adkim=s; aspf=s"
p=none means monitor only; quarantine recommends quarantining; reject recommends rejection during SMTP. adkim and aspf switch strict/relaxed. Rather than jumping straight to reject, it is safer to observe volume and legitimate sources at none, then raise the policy in stages.
Additionally, when sending rua / ruf to an external aggregation service, RFC 7489 requires an extra DNS record on the third party’s side. For example, to send example.com’s reports to thirdparty.example.net, the receiver must publish example.com._report._dmarc.thirdparty.example.net TXT "v=DMARC1".
Troubleshooting Checklist
Finally, a checking order you can use in the field as is. For contact-form non-delivery, working down from the top is the fast path.
Check First
- Is
From:on your own domain - Is the user’s address in
Reply-To: - Does
Authentication-Resultsshowspf=passordkim=pass, and additionallydmarc=pass - If
dmarc=fail, is it an authentication failure or an alignment failure - Is SPF consolidated into one record
- Are SPF’s lookup counts not excessive
- Can the DKIM public key be resolved
- Does DMARC have a
p= - Are the sending IP’s PTR and reverse DNS reasonable
- Are you on a shared IP, and if so has its reputation deteriorated
Where to Look in Bounces and Logs
If bounce mail is arriving, the important fields inside the message/delivery-status DSN are Final-Recipient, Status, Action, and Diagnostic-Code. RFC 3464 defines this machine-readable delivery-failure information. A line like Diagnostic-Code: smtp; 550 relay not permitted means the rejection happened at the SMTP layer, not the application layer.
On the server side, look at least at the MTA’s delivery logs. With Postfix you will see delivery successes and failures, queue backlog, relay refusals, DNS resolution failures, and DKIM milter warnings. Same with Exim. Representative commands:
# Example: Postfix
journalctl -u postfix -n 200 --no-pager
postqueue -p
# Example: Exim
exim -bp
Log paths and command permissions vary by hosting, so first confirm whether you can see “the mail delivery logs”, not “the application logs”. In environments where these are invisible, consolidating onto external SMTP makes problems much easier to analyze.
How to Look in Gmail and Outlook
In Gmail, “Show original”; in Microsoft Outlook, “Message details” or “Internet headers” expose the raw headers. For contact-form non-delivery investigations, saving and comparing the full header text beats screenshots.
Reading Receiving-Side Errors
Gmail-family errors are easy to read causes from.
| Example error | Meaning | Main remedy |
|---|---|---|
5.7.27 |
SPF failure | Add the source to the SPF record |
5.7.30 |
DKIM failure | Fix the DKIM key / signing configuration |
4.7.32 |
From: does not align with the SPF/DKIM organizational domain |
Revisit the From: design |
5.7.25 |
PTR / reverse DNS deficiency | Fix the sending IP’s reverse DNS |
Google’s FAQ also spells out these errors and the remedies. The one most common with contact forms is 4.7.32 — the alignment mismatch.
The Final Yardstick
If the following three conditions hold simultaneously, the contact-form notification design can be called solid.
From:is underexample.comReply-To:is the form user’s addressAuthentication-Resultsshowsdmarc=pass
With these three in place, the design is sound whether you use SendGrid, SES, Mailgun, shared hosting, or an SMTP library. Conversely, if even one is missing, suspecting the From: design first is the shortest route.
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Designing Bulk Email for Small Businesses Without Locking Into a Specific Service
We lay out how a small business can avoid Bcc blast sending and design announcement email at the scale of dozens to hundreds of recipient...
Why Your Company Should Have a Website - Going Beyond a Brochure and Driving Profit
We lay out why a company should have a website and how it leads to profit within the flow from search to comparison, inquiry, and winning...
How to Build Service Pages - An Organizing Procedure for Technical B2B
For technical B2B sites, we lay out how to organize the role, headings, copy, CTAs, and inquiry flow of a service page.
Best Practices for Designing Chatbots That Actually Help in Business
To make a chatbot genuinely useful in business, you need to sort out its purpose, knowledge sources, permissions, handoff conditions, and...
Why PPAP Is Bad for Email Security, and What to Do Instead
We examine why PPAP — emailing a password-protected ZIP and then sending the password in a separate email — is dangerous, and lay out the...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Web Development & SEO Topics
Topic hub for website development, SEO, inquiry flow, and internal-link design.
Where This Topic Connects
This article connects naturally to the following service pages.
Website Development
Contact form design, sender-address operations for notification emails, and Reply-To design — sorting out the whole inquiry flow — is a topic that pairs naturally with website development.
Technical Consulting & Design Review
Choosing among SPF / DKIM / DMARC, external SMTP services (SendGrid / SES / Mailgun), shared hosting, and PHP mail() is easy to handle as a design review tailored to your current configuration and requirements.
Author Profile
Profile page for the article author.
Go Komura
Representative of KomuraSoft LLC
Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.
Public links