How to send email reliably on the internet
This is a work in progress. Sending email reliably isn’t an easy thing to do. These are some of my personal notes of things that have worked for me as well as some reference articles from other people who have also spent a lot of time working on this problem.
Sample dkim header
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=0; d=mail.greatclips.com; h=Message-ID:List-Unsubscribe:MIME-Version:From:To:Date:Subject:Content-Type; i=greatclips@mail.greatclips.com; bh=ygnW58q8p3EZxckvuXDdtK0cs26o6UfWAMzbc4CrfNo=; b=CgNvr+0AqaCGICrXG9kkS24/yyCLGiw1JopyxHKpGucuQSlOcWXoRfkF6e58YD60A7wh9OvjRnax vLQSq5B18D8ajwVrKtEU83dUp7H8BUkUjL3Y3Ae0LmsOkDphc1fEaFPnayfIauE5NYNNICJZfn74 Km+XB06HaADLkD2ETfg=
- d: Domain: The email domain you’re sending email on behalf of (eg mail.greatclips.com)
- s: Selector: A public key can be found at $s._domainkey.domain
- The public key is stored in a TXT type dns record
- Fetch the record with
dig 0._domainkey.mail.greatclips.com txt
in this example
Email auth headers
- dkim=pass DKIM signature check passed on the receiving esp side
- spf=pass SPF check passed on the receiving esp side
- dmarc=pass (p=NONE sp=NONE dis=NONE)
- policy=NONE Can be either NONE, QUARANTINE or REJECT. Usually when you turn on dmarc you start in policy mode NONE so you can start collecting reports on how well you are aligned with you message from header, return-path (for spf) and dkim signature header
- sp=NONE tells the receiving esp whether to apply dmarc policy to subdomains
- dis=NONE Gmail specific extension (Are others doing it?) saying policy=. was ignored (Happens when mail is forwarded?) source
- DNS
- Sending email
- Reverse (PTR “pointer” records): A ptr record must be setup mapping an ip address to the email sending domain (hence the “reverse” dns part since dns usually takes a domain and returns 1 or more ip addresses). For AWS and Azure I can set this up myself but less progressive hosting providers may need a phone call and a special request
-
-> mail.leskowsky.net (PTR record)
-
- Forward: The email sending domain should have a dns “A” record setup pointing at the vm running postfix (or other mail transport agent (mta))
- mail.leskowsky.net ->
(A record)
- mail.leskowsky.net ->
- SPF
- DKIM
- DMARC
- Reverse (PTR “pointer” records): A ptr record must be setup mapping an ip address to the email sending domain (hence the “reverse” dns part since dns usually takes a domain and returns 1 or more ip addresses). For AWS and Azure I can set this up myself but less progressive hosting providers may need a phone call and a special request
- Receiving email
- MX
- Sending email
- Reputation
- IP
- Warming
- Domain
- Domain splitting: It’s a good idea to isolate different kinds of email from each other through the use of subdomains. (eg Transactional email vs bulk or marketing email is an easy example to think about)
- no-reply@mail.company.com (or no-reply@company.com) vs no-reply@marketing.company.com
- Reputation of email sent at the root domain affects email sent from subdomains. The reverse is not true.
- Email sent from 1 subdomain doesn’t impact the rep of email sent from another subdomain
- Domains and subdomains must be warmed for sending email (Google and Microsoft offer tools to see a domain’s rep from their point of view)
- Every email service provider will have their own internal way of measuring email domain rep involving multiple factors
- IPs have rep too but it is thought that since services exist that provide shared pools of ip addresses for use by multiple senders this signal is not as good as it was for ESPs
- If you split too much you may get to a place where you’re not sending enough email in a channel for it to build the rep it needs for email to deliver
- Domain splitting: It’s a good idea to isolate different kinds of email from each other through the use of subdomains. (eg Transactional email vs bulk or marketing email is an easy example to think about)
- IP
- Postfix
- Transactional vs bulk (unsolicited) email
- Feedback loops: Register with email service providers so that you can receive notifications whenever a recipient marks one of your emails as spam. Add these mailboxes to a do-not-email list to protect your email domain / ip rep. The ESPs you’re probably particularly worried about not offending are gmail, outlook / hotmail and yahoo
Gmail
- Prevent mail being blocked or marked spam: Google’s guidance to sending email reliably to gmail mailboxes
- Postmaster tools: Postmaster tools lets you analyze delivery gmail reports for your domain
Microsoft, outlook, hotmail
- Guidelines for sending email to hotmail mailboxes
- Summarized by returnpath
- SNDS: Smart Network Data Services (Reputation of sender with Outlook.com)
Links
From the trenches
Message authentication
SPF
DKIM
- DKIM: What is it and why is it important?
- DKIM Selectors: When you’re sending email from multiple delivery providers on a single domain. Eg GSuite and Salesforce both sending email on behalf of leskowky.net
- DKIM Records Explained
DMARC
- What is DMARC and why is it important?
- DMARC: What is it and why do you need it?
- How DMARC and a custom Return-Path work together
- What if DKIM passes on a message sent from an unfamiliar source?
- DMARC reports
- From Google
- DMARC Monitoring Tools — Comparison Sheet
- What is DMARC Identifier Alignment: Describes spf, dkim alignment pretty well me thinks.
Tools
Using email subdomains
- When and why to use them from Litmus: Highly level overview of why / how you’d do it with real examples from internet companies actually doing it
Sample DMARC records
[cleskowsky@ip-10-0-10-192.ec2.internal opendkim]$ dig _dmarc.amazon.com txt
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.15 <<>> _dmarc.amazon.com txt
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45656
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;_dmarc.amazon.com. IN TXT
;; ANSWER SECTION:
_dmarc.amazon.com. 273 IN TXT "v=DMARC1;" "p=quarantine;" "pct=100;" "rua=mailto:report@dmarc.amazon.com;" "ruf=mailto:report@dmarc.amazon.com"
;; Query time: 1 msec
;; SERVER: 10.0.0.2#53(10.0.0.2)
;; WHEN: Fri Nov 17 18:00:18 EST 2023
;; MSG SIZE rcvd: 162
[cleskowsky@ip-10-0-10-192.ec2.internal opendkim]$ dig _dmarc.google.com txt
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.15 <<>> _dmarc.google.com txt
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14921
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;_dmarc.google.com. IN TXT
;; ANSWER SECTION:
_dmarc.google.com. 300 IN TXT "v=DMARC1; p=reject; rua=mailto:mailauth-reports@google.com"
;; Query time: 6 msec
;; SERVER: 10.0.0.2#53(10.0.0.2)
;; WHEN: Fri Nov 17 18:00:31 EST 2023
;; MSG SIZE rcvd: 117
Generate dkim signing key
opendkim-genkey -d leskowsky.net -s k1