- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Why Your Emails Land in Spam: SPF, DKIM and DMARC Explained
About Post
The password reset feature works perfectly. The code runs, the mail is sent, the log says success. The user just never sees it, because it's sitting in their spam folder between a lottery win they never entered and an invoice for something they never ordered.
Nothing in your app is broken. The problem is that the receiving mail server doesn't believe the email is really from you. And by the rules of email, it's right to be suspicious.
Three DNS records fix most of this: SPF, DKIM and DMARC. They sound like alphabet soup, but each one answers one simple question.
Why email needs proof at all
Email was designed in a friendlier era. The protocol that moves it, SMTP, lets the sender write any From address they like. Nothing in the basic protocol stops a server somewhere from sending mail "from" your domain.
Spammers and phishers noticed a long time ago. So modern receivers (Gmail, Outlook, Yahoo and the rest) check every incoming message for evidence that the domain in the From line actually authorised it. No evidence, lower trust. Low trust plus anything else slightly odd, and you're in spam or rejected outright.
That checking got stricter for everyone in 2024, when Google and Yahoo started requiring bulk senders to authenticate their mail with SPF, DKIM and DMARC. Even if you only send a handful of receipts a day, the same checks decide where your mail lands.
SPF: who is allowed to send
SPF (Sender Policy Framework) is a list, published in DNS, of the servers allowed to send mail for your domain. Think of it as a guest list at the door.
example.com. TXT "v=spf1 include:spf.your-provider.example ~all"
This says: servers listed by my email provider may send for me, and anything else should be treated with suspicion (~all is a "soft fail"; -all is a hard fail).
The gotchas that bite in production:
- Only one SPF record per domain. Add a second one for your new marketing tool and both become invalid. Merge them into one record with two
includes. - There's a limit of 10 DNS lookups. Every
includecan trigger more lookups. Stack enough services and SPF fails with a "too many lookups" error. - SPF checks the envelope sender (the hidden
Return-Path), not the visibleFrom. That detail is why SPF alone isn't enough, and why DMARC exists. - Forwarding breaks it. When a mail is forwarded, it arrives from the forwarder's server, which isn't on your list.
DKIM: a signature that travels with the mail
DKIM (DomainKeys Identified Mail) works like a wax seal. Your sending service signs certain headers and the body with a private key. You publish the matching public key in DNS, under a "selector":
s1._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
The receiver reads the signature, fetches the public key and checks that the message wasn't changed on the way. Because the signature is part of the message, it usually survives forwarding, which SPF doesn't.
Most providers give you two or three CNAME records to add instead of a raw key, so they can rotate keys for you. The classic mistake is verifying the domain in the provider's dashboard and forgetting to add those records at all.
DMARC: tying it to the name people see
Here's the gap. SPF can pass for bounces.provider.example and DKIM can pass for provider.example, while the From line says [email protected]. Technically everything "passed", and a phisher could do the same.
DMARC closes that gap with alignment: at least one of SPF or DKIM must pass for the same domain that appears in the From line. It also lets you tell receivers what to do when that fails, and where to send reports.
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:[email protected]"
The policy p has three levels: none (just report), quarantine (send failures to spam) and reject (refuse them). Start with none, read the reports for a while to find every legitimate service sending as you (the CRM, the helpdesk, the HR system nobody mentioned), fix them, then tighten.
The three side by side
| Question it answers | Lives in DNS at | Weak spot | |
|---|---|---|---|
| SPF | Is this server allowed to send for the domain? | example.com TXT | Checks the hidden sender; breaks on forwarding |
| DKIM | Was this message signed by the domain and left unchanged? | selector._domainkey.example.com | Says nothing about the visible From on its own |
| DMARC | Does a pass match the From domain, and what if it doesn't? | _dmarc.example.com TXT | Only as good as the SPF and DKIM setup under it |
The Laravel side
Most deliverability problems I see in Laravel apps start with how mail leaves the server. Sending through PHP's mail() on a cheap VPS or shared host means your mail comes from an IP with no reputation, often no reverse DNS, and no DKIM. Use a transactional provider instead. Laravel supports several out of the box, including Amazon SES, Postmark, Resend and Mailgun.
MAIL_MAILER=ses
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
A few habits that help:
- Send from a subdomain like
mail.example.comfor transactional mail, and a different one for newsletters. A marketing campaign with lots of complaints then can't drag down your password resets. - Make the From domain match the authenticated domain. Never send "from" a Gmail address through your own server. DMARC will fail by design.
- Queue your mail with
Mail::to($user)->queue(...)or a mailable that implementsShouldQueue, so a slow provider doesn't slow your request. - Handle bounces and complaints. Providers send webhooks for them. Keep sending to dead addresses and your reputation drops.
The Laravel mail docs cover the driver setup for each provider.
How to check in 30 seconds: send yourself a mail at a Gmail address, open it, choose "Show original", and look for SPF, DKIM and DMARC. You want three PASS results. Anything else tells you exactly which record to fix.
Authentication is the ticket, not the seat
Passing all three doesn't guarantee the inbox. It proves who you are. Where you land still depends on reputation: bounce rates, spam complaints, whether people open your mail, and whether your content looks like something people asked for.
But without authentication, reputation never gets a chance. Get the three records right first, then worry about the rest.
Which one caught you out first: the duplicate SPF record, the missing DKIM CNAMEs, or a DMARC report revealing a service you didn't know was sending as you?

Be first to comment it...