How to Test SPF, DKIM, and DMARC in Your Email Pipeline
Your transactional email can pass every assertion in your test suite and still land in spam. Tests usually check that a message was sent and what it says — not whether it’s authenticated. Here’s how to check SPF, DKIM, and DMARC by actually sending a message and reading the result, right in CI.
A 60-second refresher
Three DNS-based mechanisms decide whether a receiving server trusts your mail:
- SPF — is this sending IP allowed to send for the envelope domain?
- DKIM — is the message cryptographically signed by the domain, with the signature intact?
- DMARC — given the SPF/DKIM results and their alignment with the
From:domain, does its policy say to accept the message?
Get one wrong and mailbox providers quietly route you to spam — or reject outright. The catch: these only fail in the real send path, which is exactly what unit tests don’t exercise.
Test them by actually sending
A static DNS checker tells you your records parse. It doesn’t tell you that the message your app actually sent — from the IP your provider actually used, with the signature your library actually attached — passes. To know that, send a real message and inspect how a receiving server graded it.
Send to a disposable Devmailr inbox and read the verdicts the receiving mail server computed:
const inbox = await createInbox(`deliv-${Date.now()}`)
// send a real message from your app / ESP to inbox.address
await sendTransactionalEmail({ to: inbox.address, template: 'welcome' })
const email = await waitForEmail(inbox._id)
console.log(email.verdicts)
// { spf: 'PASS', dkim: 'PASS', dmarc: 'PASS', spam: 'PASS', virus: 'PASS' }Reading the verdicts
Every inbound email carries a verdicts object. Each value is one of:
PASS— the check passed.FAIL— it failed (this is what sends you to spam).GRAY— inconclusive or not applicable.PROCESSING_FAILED— the check couldn’t be evaluated.
The keys are spf, dkim, dmarc (authentication) plus spam and virus (content scan). A key is present only if the receiving server reported it.
Assert it in CI
Now make a broken DKIM signature fail the build, not your users’ inboxes:
const { verdicts } = await waitForEmail(inbox._id)
expect(verdicts.spf).toBe('PASS')
expect(verdicts.dkim).toBe('PASS')
expect(verdicts.dmarc).toBe('PASS')Run that after you rotate a DKIM key, switch email provider, or move sending infrastructure — the three moments email authentication usually breaks silently.
Spam and virus, too
The same object surfaces the content scan. A spam verdict of FAIL on a message you control is an early warning that your template or sending reputation is tripping filters:
if (verdicts.spam !== 'PASS') {
console.warn('Receiving server flagged this message as spam')
}Why this beats a DNS checker
- It tests the real path — your actual sending IP and signature, end to end.
- It catches regressions a record-parser can’t: an unsigned message, a misaligned
From:, an IP that fell off your SPF include. - It runs on every deploy, so a deliverability regression fails fast instead of quietly tanking your open rates.
Good to know: Verdicts are computed by the receiving server at receive time and are available on every plan — on GET /emails/:id, the list endpoint, /wait, and the email.received webhook.
Related reading
- How to test email in your CI/CD pipeline — the foundation this builds on.
Try it in two minutes
Create a disposable prefix@devmailr.app inbox over the API, send mail to it from your app, and read it in your tests. Free plan, no card required.