Laravel Emails Going to Spam: SPF, DKIM and DMARC Checklist
Laravel can send an email successfully while the recipient still sees it in spam. Diagnose SMTP settings, SPF, DKIM and DMARC with practical code and DNS examples.

If your Laravel emails are going to spam, start with the message's authentication results and the service that actually sends it. Check your SMTP or API configuration, verify SPF and DKIM, and confirm that at least one passing authentication domain aligns with your visible From address for DMARC.
A successful Mail::send() call is not proof of inbox delivery. Your transport may accept the message, while a receiving provider later rejects it, places it in spam or applies additional filtering.
This guide helps you diagnose password resets, verification messages, invoices and other Laravel emails without changing random DNS settings or rewriting every template.
1. Confirm how Laravel sends your email
Trace one affected email from your application to the recipient. A local preview, an SMTP acceptance response and an inbox placement result describe different stages.
| Observation | Next check |
|---|---|
| No message reaches the sending provider | Mailer selection, queue workers, failed jobs and application logs. |
| The provider accepts the message | Its delivery events, bounces and recipient response. |
| The email appears in spam | Recipient headers, authentication, reputation and content. |
| Only one mail type is affected | Compare its sender override, queue, template, links and transport with a working message. |
Check config/mail.php and any explicit Mail::mailer(...) calls. Also inspect mailables and notifications that override the sender. A working SMTP connection does not mean every message uses the same From domain.
2. Check your Laravel SMTP configuration
Here is an illustrative configuration for an authenticated SMTP submission service. Replace every example value with the settings supplied by your provider:
MAIL_MAILER=smtp
MAIL_HOST=smtp.provider.example
MAIL_PORT=587
MAIL_USERNAME=your-smtp-username
MAIL_PASSWORD="replace-with-your-smtp-password"
MAIL_FROM_ADDRESS=notifications@example.com
MAIL_FROM_NAME="Example App"
The host above is a placeholder. Confirm the provider's port and TLS requirements. In newer Laravel skeletons, SMTP configuration reads MAIL_SCHEME; older applications may use MAIL_ENCRYPTION. An upgraded project can retain its older configuration file, so inspect the actual environment variables read by your app.
For a configuration using scheme => env('MAIL_SCHEME'), a provider may specify smtp with STARTTLS on port 587 or smtps with implicit TLS on port 465. Follow its instructions and keep certificate verification enabled. Do not paste MAIL_ENCRYPTION=tls into a project that never reads it and assume TLS is configured.
Keep credentials out of source control. Do not expose an environment dump or a public diagnostic route while debugging.
Refresh configuration and long-running workers
After updating production settings, rebuild the configuration cache as part of your normal deployment:
php artisan config:cache
php artisan queue:restart
The second command requests a graceful restart of standard queue workers; your process manager must start them again. If you use Horizon, use its deployment procedure, including php artisan horizon:terminate. Reload other long-running application processes when applicable.
To inspect only the relevant non-secret settings in Tinker:
php artisan tinker
config('mail.default');
config('mail.from.address');
config('mail.mailers.smtp.host');
config('mail.mailers.smtp.port');
A queued message that has not been sent is a queue problem, not an inbox-placement problem. Check your queue dashboard, failed jobs and provider events before editing DNS.
3. Verify SPF for the envelope sender domain
SPF authorizes sending servers for the SMTP envelope sender, usually reflected in the delivered message's Return-Path. This can differ from MAIL_FROM_ADDRESS.
For an application that sends directly from a dedicated SMTP server, an illustrative TXT record is:
; Documentation-only IP: replace it with the actual outbound SMTP IP.
example.com. TXT "v=spf1 ip4:192.0.2.10 -all"
This record does not work for a third-party provider unless that is its actual authorized sending IP. For hosted delivery services, use the SPF or custom return-path setup they provide. Do not authorize your web server simply because it runs Laravel: the outbound sender may be a completely different machine.
- Publish one SPF policy per evaluated domain, rather than multiple separate
v=spf1records. - Merge authorized services into that policy.
- Check nested
includemechanisms: SPF limits DNS-querying terms to 10 during evaluation. - Evaluate the actual envelope sender domain, including a bounce subdomain if your provider uses one.
dig +short TXT example.com
dig +short TXT bounce.example.com
For lookup-limit errors, read SPF Too Many DNS Lookups. Use the SPF record checker to inspect the published policy. DNS inspection shows configuration; a received test message shows whether that specific delivery passed.
4. Enable DKIM signing at your sending provider
DKIM attaches a cryptographic signature to a message. The receiver retrieves a public key from DNS and verifies the signature. With a hosted mail service, enable domain authentication in its dashboard and publish the exact TXT or CNAME records it supplies.
A simplified TXT example looks like this; the placeholder is deliberately not a usable key:
selector1._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=YOUR_PROVIDER_PUBLIC_KEY"
Do not generate an unrelated DNS key without configuring the matching private key on the signing service. Publishing a record alone does not make Laravel emails DKIM-signed.
Inspect a real message's signature:
DKIM-Signature: v=1; a=rsa-sha256;
d=example.com; s=selector1; ...
Here, d= identifies the signing domain and s= the selector. Query their combination:
dig +short TXT selector1._domainkey.example.com
dig +short CNAME selector1._domainkey.example.com
See also how to find your DKIM selector. The DKIM checker helps inspect the selector's DNS configuration. To confirm signature validity, test an actual sent message. A valid public key alone cannot prove that a message was correctly signed.
5. Check DMARC alignment, not just SPF and DKIM passes
DMARC connects authentication to the domain recipients see in the From header. It passes when at least one mechanism passes and aligns: SPF with an aligned envelope sender, or DKIM with an aligned signing domain.
| Message identity | Illustrative value |
|---|---|
| Visible From | notifications@example.com |
| Envelope sender | bounce@provider.example.net |
| DKIM signing domain | provider.example.net |
In this example, SPF and DKIM can both pass for the provider's domain while DMARC fails for example.com. Configure custom DKIM signing for your domain, or an aligned custom envelope sender supported by the provider.
With relaxed alignment, bounce.example.com and example.com can align at the organizational-domain level. Strict alignment requires an exact domain match for the relevant mechanism.
A monitoring policy can help you inventory legitimate senders before enforcement:
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
Create and monitor the reporting mailbox or use a report-processing service. p=none requests monitoring; it does not make unauthenticated messages pass or guarantee inbox delivery. After reviewing legitimate sending sources, consider a staged move toward quarantine or reject.
dig +short TXT _dmarc.example.com
See also why SPF and DKIM pass but DMARC fails. Use the DMARC record checker for the DNS policy, then inspect a delivered message for the authentication outcome.
6. Send a real test from Laravel
First, use a minimal message to isolate the sending configuration. In Tinker, replace the recipient with the unique address provided by MXValidation's email test:
use Illuminate\Support\Facades\Mail;
Mail::raw('This is a Laravel mail configuration test.', function ($message) {
$message->to('REPLACE_WITH_YOUR_TEST_ADDRESS')
->subject('Laravel mail configuration test');
});
This sends synchronously and bypasses your normal queue. If it works but production notifications do not, compare the normal queue path and per-message settings.
Next, send the actual mailable with dummy data through the same transport and queue used in production. Test password resets and verification messages as well as generic mail: they may use different notification configuration. Generate a fresh test address for each MXValidation message.
A receiver's authentication header might contain:
Authentication-Results: receiving.example;
spf=pass smtp.mailfrom=bounce.example.com;
dkim=pass header.d=example.com;
dmarc=pass header.from=example.com
Read the trusted receiving system's results, not an arbitrary header copied into a message. Check which domain passed, not only the word pass.
What the test proves: MXValidation checks technical authentication and spam-related signals. It does not measure placement inside Gmail or Outlook. Send separate tests to inboxes you control to observe their filtering.
7. SPF, DKIM and DMARC pass, but Laravel mail still goes to spam
Authentication is a foundation, not a reputation score. Once the message passes, investigate these areas:
- Sending reputation: review provider events, complaints, bounces and recent volume changes. A new domain or a problematic shared IP can affect delivery.
- Infrastructure: if you operate the outbound SMTP server, check PTR, matching forward DNS, its hostname and TLS. These are usually managed by a hosted sending provider.
- Recipient expectations: send messages people requested, suppress hard bounces and honor opt-outs for subscribed mail.
- Message quality: use an accurate subject, recognizable sender, working HTTPS links and useful text. Inspect your actual rendered HTML and plain-text alternative.
- Contact forms: keep From on your authenticated domain. Put the visitor's validated address in Reply-To rather than impersonating their domain.
- Sending streams: consider separating marketing and transactional traffic with provider streams or subdomains, while authenticating each correctly.
Do not change providers after every spam-folder result. Compare multiple test messages and recipient providers, then change one relevant factor at a time.
Frequently asked questions
Does changing MAIL_FROM_ADDRESS fix Laravel emails going to spam?
It can fix a mismatched identity when the new address uses an authenticated domain. It does not independently enable SPF or DKIM. Re-test DMARC alignment after the change.
Should I use SMTP or an API driver?
Either can deliver authenticated email. Choose a supported transport for your provider and operational needs. Switching to an API does not remove the need for domain authentication, bounce handling or reputation management.
Do I need to add an MX record to send Laravel emails?
MX records primarily route incoming mail. Adding one is not a general fix for outgoing spam. Some providers require MX records for a custom bounce domain; follow their specific setup.
Can Laravel sign DKIM messages itself?
Signing can be implemented in the application or mail infrastructure, but a hosted provider normally handles it. Start with its domain-authentication workflow rather than introducing application-level key management.
How long after a DNS change should I test again?
It depends on the previous TTL and resolver caches. Check the authoritative DNS response, allow cached records to expire, then send a new message. Old messages retain their original authentication results.
Final Laravel email checklist
- The intended SMTP or API mailer is active in production.
- Configuration is refreshed and relevant workers have reloaded it.
- The visible sender uses a domain you control.
- The actual envelope sender passes SPF.
- The message has a valid DKIM signature.
- At least one passing authentication method aligns for DMARC.
- A real application message has been tested through the production sending path.
- Delivery events, recipient filtering and reputation are reviewed separately.
Run a free email deliverability test to inspect your Laravel message, identify failed checks and prioritize the next fix.
