Deliverability guide
Do your emails land in the inbox or in spam? This guide reviews what the major providers' filters actually look at in 2026 — not the myths from the 2000s — then details SPF, DKIM and DMARC configuration backed by the RFCs, and explains how to set up your own SMTP server, on a VPS or with AWS SES. Every step can be verified with the spam test.
1. Content: what filters really look at
First, a clarification: the famous "trigger words list" (free, urgent, win...) is a myth dating back to the rule-based filters of the 2000s. Since then, Gmail, Outlook and similar providers use machine learning models trained on billions of messages. A single word no longer triggers anything: it's the whole set of signals that matters.
The real signals taken into account
- the state of authentication (SPF/DKIM/DMARC and their alignment) — the #1 criterion;
- the domain's age and its reputation history;
- the IP's reputation and the sending volume curve (a sudden spike is a signal);
- recipient engagement: opens, replies, delete without reading, spam reports;
- structural characteristics: HTML/text ratio, number of links, image/text ratio;
- semantic similarity with known spam patterns (embeddings).
What actually hurts inbox placement
- High link density: 3 or more links in a short message, especially with URL shorteners (bit.ly, tinyurl, t.co).
- Drowned content: a simple message wrapped in kilobytes of HTML with inline CSS, or an inverted image/text ratio (80% image / 20% text).
- Identical mass sends: the same body sent to thousands of recipients within a 24-hour window.
- Attachment on first contact: a strong phishing signal.
- Misleading subjects: a false "Re:" or "Fwd:" on a genuine first contact, "URGENT REQUEST", "Last Chance", or the recipient's name in the display name. Gmail explicitly classifies this as deceptive.
- Hidden content: text hidden with CSS (display:none, 1px font) — hiding content sends mail to spam.
- "Reply STOP to unsubscribe" in outreach: classified as marketing, therefore subject to unsubscribe requirements.
The residual "old-school" patterns (ALL CAPS, !!!, $$$, "100% free", currency symbols in the subject) still count, but as a statistical correlation, not as an absolute rule.
The golden rules of content
- Factual tone, clear identity: accurate, non-misleading headers, a display name that identifies the sender alone.
- Text/image ratio: 60:40 text/image for a marketing newsletter, 95%+ text for individual outreach. A message must remain understandable with images blocked.
- Visible, understandable links: descriptive anchors, no URL shorteners.
- Separate your streams: one From address per message category, transactional and promotional on distinct IPs.
- One-click unsubscribe — a Gmail requirement for any marketing message (RFC 8058):
List-Unsubscribe-Post: List-Unsubscribe=One-Click
List-Unsubscribe: <https://votredomaine.com/desinscription>
The URI must use HTTPS and respond to a simple POST, with no navigation. A visible link in the body is required, and requests must be honored within 48 hours. Mailto links or a mere preferences page are not sufficient. Transactional messages (booking confirmation, password reset) are excluded from the one-click requirement.
The factual check: send your draft to a disposable address from the
spam test. The score and the AI analysis (SPF, DKIM, DMARC, content, blacklists) tell you exactly where you stand — before sending to your real recipients.
2. SPF, DKIM, DMARC, BIMI: authentication
These mechanisms prove to the receiving server that your email genuinely comes from you. They are checked systematically, and Gmail requires them for any bulk sending. The RFCs below are authoritative — the figures quoted are taken from them.
2.1 SPF — RFC 7208
SPF publishes, as a TXT record, the list of IPs authorized to send for your domain. Mechanisms: ip4, ip6, a, mx, include, and the record always ends with all.
v=spf1 include:_spf.google.com ~all # e.g. sending via Google Workspace
v=spf1 ip4:91.134.23.122 -all # e.g. dedicated server
- The 10-lookup limit (RFC 7208 §4.6.4, exact quote): "SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation". The terms concerned: include, a, mx, ptr, exists, redirect. Beyond that: permerror, the record fails at many receivers.
- The record must stay under 512 octets (RFC 7208 §3.4) to avoid DNS truncation without EDNS0.
ptr is explicitly discouraged (high DNS cost, fragile result) — and it counts toward the 10 lookups.~all (softfail) during the transition phase, -all (hardfail) once the list is complete.- A single TXT record per domain, no concatenation. If exceeded: flatten or reduce the includes.
2.2 DKIM — RFC 6376 + RFC 8301
DKIM cryptographically signs certain headers and the body; the public key is published as a TXT record on selector._domainkey.yourdomain.com.
mail._domainkey TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
- Key length (RFC 8301): RSA of at least 1024 bits is mandatory to sign, 2048 bits recommended. Gmail requires a minimum of 1024 bits and recommends 2048.
- Algorithm:
rsa-sha1 is forbidden, rsa-sha256 is mandatory. - Rotation: publish a new key on a new selector, keep both in DNS during the transition, remove the old one after 1-2 years.
- DNS pitfall: a 2048-bit RSA key exceeds 255 characters — you must split the TXT value into several strings (allowed by the DNS standard).
2.3 DMARC — RFC 7489
DMARC, published on _dmarc.yourdomain.com, tells the receiver what to do with messages that fail SPF/DKIM: none, quarantine or reject.
_dmarc TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@votredomaine.com; adkim=r; aspf=r"
- Alignment (RFC 7489 §3.1): relaxed = the organizational domain must match (d=example.com aligned with From news.example.com); strict = exact FQDN match. A pass on SPF OR DKIM is enough to pass DMARC — Gmail recommends dual alignment and announces it as mandatory in the future.
- Recommended rollout:
p=none with rua= to observe (aggregate reports arrive from Gmail and Microsoft), fix the non-aligned sources, then tighten to quarantine then reject. - Gmail: DMARC mandatory for bulk senders, p=none minimum. Microsoft: the same beyond 5,000 messages/day, with possible rejection (code 550 5.7.515) if absent.
2.4 BIMI — good to know, not essential
BIMI displays your logo next to your emails. A point of rigor: it's an IETF Internet-Draft (not an RFC), and it does not directly improve placement — it's a visual trust indicator.
- Requirements: DMARC at quarantine or reject (p=none excluded), an SVG Tiny PS logo served over HTTPS, a record on
default._bimi.yourdomain.com. - Gmail requires a VMC (Verified Mark Certificate): issued only by DigiCert or Entrust, around 1,500 USD/year, requires a registered trademark. Apple, Yahoo and Fastmail display it without a VMC; Outlook does not support it.
| Configuration | Result for the receiver |
|---|
| Aligned SPF + DKIM + DMARC (reject) | Maximum trust |
| SPF only, no DKIM | Partial — spam risk at Gmail |
| None of the three | Almost certainly in spam |
A 10-second check: send an email to an address from the
spam test — the report details SPF, DKIM and DMARC with the exact verdict of each mechanism and the fixes to apply.
3. Set up your own SMTP server on a VPS (Postfix + OpenDKIM)
A self-hosted SMTP gives you total control, but the infrastructure is demanding: PTR, SPF/DKIM/DMARC, TLS, and progressive IP warm-up. Without this, your IP will be blacklisted within a few days.
Step 1 — Install
sudo apt install postfix opendkim opendkim-tools
opendkim-tools provides opendkim-genkey — a classic pitfall: "command not found" without this package.
Step 2 — Configure OpenDKIM
# /etc/opendkim.conf — the settings that change everything:
Mode sv # v = verification only by default; sv enables signing
Domain votredomaine.com
RequireSafeKeys true # refuses to sign if the private key is readable by others
Socket inet:8891@localhost
# then enable KeyTable, SigningTable, ExternalIgnoreList, InternalHosts
Step 3 — Generate the keys
sudo mkdir -p /etc/opendkim/keys/votredomaine.com
sudo opendkim-genkey -b 2048 -d votredomaine.com -D /etc/opendkim/keys/votredomaine.com -s mail
sudo chown -R opendkim:opendkim /etc/opendkim/keys
sudo chmod 600 /etc/opendkim/keys/votredomaine.com/mail.private
-b 2048: complies with RFC 8301 and Google's recommendation. Then publish the value of the generated mail.txt file in the mail._domainkey TXT record (split into several strings if > 255 characters).
Step 4 — Hook up Postfix
# /etc/postfix/main.cf:
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
# SigningTable:
*@votredomaine.com mail._domainkey.votredomaine.com
# KeyTable:
mail._domainkey.votredomaine.com votredomaine.com:mail:/etc/opendkim/keys/votredomaine.com/mail.private
sudo systemctl restart opendkim postfix
opendkim-testkey -d votredomaine.com -s mail -vvv # validates the DNS publication
Step 5 — PTR, an absolute prerequisite
Google requires it for ALL senders: "The sending IP address must match the IP address of the hostname specified in the Pointer (PTR) record". The hostname must also have an A/AAAA record pointing to the same IP (forward DNS). Its absence or inconsistency produces 4.7.23/5.7.25 errors at Gmail. The PTR is configured in your hosting provider's panel (OVH, Hetzner, Scaleway...), not in your DNS.
Step 6 — TLS and port 25
- TLS is mandatory to relay to Gmail (4.7.29/5.7.29 errors otherwise).
- Port 25 is the server-to-server relay port. Many ISPs and clouds restrict it by default (AWS throttles it on EC2, an unblock request is required).
- For submission from a client, use 587 (STARTTLS) or 465 (implicit TLS).
- Hosting provider's network firewall: ufw is not always enough — also check the provider's application firewall.
Step 7 — Warm up and monitor
- A new IP starts from zero reputation, and the domain has ITS own reputation (especially at Gmail). A typical warm-up ramps up gradually over 4-6 weeks, targeting the most engaged recipients first. Massive volume on day one = throttling, greylisting or blocking.
- Spamhaus blacklists: SBL (spam/malware), PBL (IPs that should not send), XBL (compromised machines), DBL (spam domains), ZEN (SBL+PBL+XBL). A listed IP is almost systematically rejected.
- Bounce rate: hard bounce < 1% on a healthy list (observed average ~0.2%), repeated soft bounces over 3 campaigns = treat as a hard bounce. Above 5%: an active list-quality problem.
- Complaint rate: < 0.1% at Gmail, < 0.3% at Yahoo. Double opt-in is the best safeguard.
- Monitoring: Google Postmaster Tools (IP + domain reputation, spam rate), SNDS and JMRP at Microsoft.
After each step, send a test email to the disposable address from the
spam test: the report confirms SPF, DKIM, DMARC, blacklists and the SpamAssassin score in real time.
4. Set up your SMTP server with AWS SES
Amazon SES (Simple Email Service) outsources the infrastructure: no server to maintain, a reputation managed by AWS, and a pay-per-send cost ($0.10 per 1,000 emails). The trade-off: quotas to raise, identities to verify, and strict consent rules.
Step 1 — The sandbox, then production
- Every new SES account starts in the sandbox (per region): max 200 messages/24 h, 1 message/second, only to verified addresses or the mailbox simulator.
- Moving to production: first verify an identity (preferably a domain — this speeds up approval), then console → Account dashboard → Request production access, declaring the traffic type (marketing or transactional) and the site URL. AWS support responds within 24 h.
- Via CLI:
aws sesv2 put-account-details --production-access-enabled --mail-type TRANSACTIONAL --website-url https://yoursite.com --additional-contact-email-addresses contact@yoursite.com --contact-language FR
Step 2 — Verify the domain
SES gives you the records to publish at your registrar: 3 DKIM records (CNAME) managed automatically, 1 MX (bounces), 1 SPF TXT. SPF and DKIM are therefore handled for you — the major advantage over a self-hosted VPS. All identities used in From/Source/Sender/Return-Path must remain verified, including in production.
Step 3 — SMTP credentials
Console SES → SMTP settings → Create SMTP credentials
# generates an IAM user + a dedicated SMTP password
email-smtp.eu-west-3.amazonaws.com:587 # STARTTLS
email-smtp.eu-west-3.amazonaws.com:465 # implicit TLS
# Python example (smtplib)
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "contact@votredomaine.com"
msg["To"] = "client@exemple.com"
msg["Subject"] = "Votre facture est disponible"
msg.set_content("Bonjour, votre facture de septembre est en ligne.")
with smtplib.SMTP("email-smtp.eu-west-3.amazonaws.com", 587) as s:
s.starttls()
s.login("VOTRE_UTILISATEUR_SMTP", "VOTRE_MDP_SMTP")
s.send_message(msg)
Step 4 — Quotas and best practices
- Outside the sandbox: a common initial quota of 50,000 messages/24 h, adjustable according to declared usage. Max 50 recipients per message (To/CC/BCC combined), 10 MB per message via API v1, 40 MB via API v2 or SMTP.
- Configuration sets + event publishing (SNS/EventBridge) to monitor bounces and complaints, and immediately remove hard bounces. Keep the complaint rate under 0.1%.
- Increase volume gradually, in line with Gmail's volume growth rules.
- EC2 pitfall: if sending goes through an EC2 instance, you must request the unblocking of the instance's port 25 throttle.
- The production access request requires declaring that you only target consenting recipients — that's AWS's anti-spam filter.
VPS or AWS: the choice
| Self-hosted VPS (Postfix) | AWS SES |
|---|
| Cost | IP + VPS (fixed) | $0.10 / 1,000 emails |
| Maintenance | Postfix, OpenDKIM, blacklists, PTR | None (managed) |
| IP reputation | Build it yourself | Managed by AWS |
| Volume | Unlimited | Quotas to raise |
| Ideal for | Huge volume, total control | Quick start, low maintenance |
5. The "ready to send" checklist
- SPF published, under 10 lookups, ending with ~all or -all (RFC 7208).
- DKIM signed with rsa-sha256, key ≥ 1024 bits (2048 recommended), selector published and testable (RFC 6376 + 8301).
- DMARC published with rua=, p=none minimum; SPF or DKIM alignment verified; goal p=quarantine then p=reject (RFC 7489).
- PTR valid and consistent with forward DNS (a Gmail requirement for everyone).
- TLS enforced on transmission.
- RFC 5322 format: Message-ID present, unique headers, simple From.
- One-click unsubscribe RFC 8058 for all marketing, honored within 48 h, visible link in the body.
- Complaint rate < 0.1% (Gmail), < 0.3% (Yahoo); bounce < 2% (hard < 1%); quarterly list cleaning.
- Progressive volume: IP + domain warm-up over 4-6 weeks targeting engaged recipients.
- Simple content: max 60:40 text/image, few clear links, zero misleading patterns (false Re:/Fwd:, hidden content).
- Monitoring: Postmaster Tools (Google), SNDS/JMRP (Microsoft), Spamhaus blacklists.
Last step, always the same: send a test email and analyze it with the
spam test. The score doesn't lie.