Introduction: Why Email Authentication Should Drive Your Inbound Parsing Choice
Email authentication is not a nice-to-have - it is the trust boundary for every inbound message your application accepts. When an email parsing service validates SPF, DKIM, and DMARC and exposes the results in a predictable data model, you can deterministically decide whether to process, quarantine, or drop a message. Without this, you are parsing content you cannot trust, increasing the risk of spoofing, ticket fraud, and account takeovers.
SPF checks whether the sending IP is authorized by the domain. DKIM verifies a cryptographic signature that binds the message to a domain. DMARC layers policy and alignment on top of SPF and DKIM to determine whether the message should be trusted. Together, these controls feed your security posture and your application logic - for example, allowing a support ticket to auto-open only when DMARC passes, or requiring manual review when authentication fails.
This article compares two developer-focused inbound email pipelines for email-authentication: one that surfaces structured authentication verdicts directly in the webhook payload, and Mandrill Inbound from Mailchimp, which focuses on forwarding parsed messages and raw headers. We will cover features, code samples, performance considerations, and edge cases like forwarding and mailing lists.
How MailParse Handles Email Authentication
This platform performs authentication in the receiving MTA as soon as a message lands. The service evaluates SPF against the connecting IP and envelope MAIL FROM, validates all DKIM signatures present in the message, calculates DMARC alignment against the From domain, and optionally evaluates ARC to carry trust across forwarders.
Key behaviors and data structures:
- Normalization of results - you receive a consolidated JSON block that captures both per-mechanism signals and a top-level verdict. Example schema:
- auth.spf: result (pass, fail, softfail, neutral, none), domain, scope, reason
- auth.dkim.signatures[]: d=domain, s=selector, result, hash_alg, canonicalization, body_hash_status
- auth.dmarc: result, aligned_identifier, alignment_mode (relaxed or strict), policy_applied (none, quarantine, reject)
- auth.arc: result (pass, fail, none), chain_length, cv
- Headers preserved - the original Authentication-Results header is included in raw MIME and exposed in parsed headers so you can cross-check.
- Webhook and REST polling - both deliver the same structured fields so your downstream code does not need to parse headers.
- Multiple DKIM signatures - all signatures are evaluated and returned, not only the first pass. This is important for multi-tenant senders and forwarded mail.
- DMARC alignment - alignment is computed against the visible From domain with r or s mode, not only SPF or DKIM results, so you get a single source of truth.
- Flagging mixed outcomes - when SPF fails but a DKIM signature passes with alignment, DMARC is marked pass with alignment details. When both fail, the policy_applied field indicates how DMARC would instruct receivers.
For deeper background on how MIME structure and headers flow into these results, see MIME Parsing: A Complete Guide | MailParse. When integrating delivery of these authentication fields into your backend, the webhook signing and retry semantics are documented in Webhook Integration: A Complete Guide | MailParse.
How Mandrill Inbound Handles Email Authentication
Mandrill Inbound is an inbound parsing pipeline tied to Mailchimp. You configure routes that point to your webhooks, and the service posts events containing message content, headers, attachments, and metadata. For email-authentication, Mandrill Inbound generally relays the raw headers - including any Authentication-Results header generated by their receiving MTA - rather than exposing structured SPF, DKIM, or DMARC verdicts as top-level JSON fields.
What this means in practice:
- You can access the full raw message through fields like raw_msg or headers and extract Authentication-Results yourself.
- The DMARC pass or fail decision is not provided as a dedicated field. You need to parse DKIM and SPF results from Authentication-Results, then compute DMARC alignment in your code.
- Multiple DKIM signatures are present in the DKIM-Signature headers inside raw MIME, but there is no list of evaluated signatures, so your parser must iterate over repeated headers.
- ARC data, if present in the message, is forwarded as headers; evaluation of the ARC chain is left to the consumer.
- Because inbound is part of Mailchimp's ecosystem, it requires an account with that platform. This can be fine for teams already invested in Mailchimp and Mandrill, but it is less convenient if you want a standalone inbound-only feature set.
The approach is fair and flexible if you want full control. The tradeoff is implementation effort: you must write robust parsers for Authentication-Results, DKIM-Signature, and From alignment rules, manage edge cases, and keep up with standards updates.
Side-by-Side Comparison of Email Authentication Features
| Capability | MailParse | Mandrill Inbound |
|---|---|---|
| SPF evaluation result in structured JSON | Yes - auth.spf.result, domain, scope | Not as a field - available via Authentication-Results header parsing |
| DKIM signature evaluation list | Yes - auth.dkim.signatures[] with per-sig fields | No top-level list - DKIM-Signature headers relayed only |
| DMARC verdict and alignment details | Yes - auth.dmarc.result, aligned_identifier, policy_applied | No top-level DMARC verdict - compute from headers |
| ARC evaluation | Yes - auth.arc.result and chain metadata | Headers forwarded only - consumer evaluates |
| Authentication-Results header passthrough | Yes - preserved and exposed | Yes - included in headers/raw_msg |
| Webhook payload includes final trust decision | Yes - consolidated verdict and per-mechanism signals | No - implement your own trust decision logic |
| Standalone inbound service without Mailchimp tie-in | Yes | No - requires Mailchimp account |
| REST polling for the same authentication fields | Yes | N/A - inbound is webhook focused |
| Raw MIME access for forensic checks | Yes | Yes |
| Idiomatic developer docs for email-authentication | Yes - examples for DMARC alignment and ARC | Partial - header passthrough, no DMARC decision examples |
Code Examples: Getting Email Authentication Results in Your App
Example A: Consuming a Webhook With Structured SPF, DKIM, and DMARC
This example shows how to receive a webhook where the payload includes parsed authentication fields. You can enforce a policy that requires DMARC pass, or allows ARC-backed pass on forwarded mail.
// Node.js - Express example
const express = require('express');
const app = express();
app.use(express.json({ limit: '10mb' }));
// Verify request signature here if your provider signs requests.
app.post('/inbound-webhook', (req, res) => {
const msg = req.body.message;
const auth = msg.auth || {};
const dmarc = auth.dmarc || {};
const spf = auth.spf || {};
const dkim = (auth.dkim && auth.dkim.signatures) || [];
const arc = auth.arc || {};
// Basic policy
let trusted = false;
let reason = 'unverified';
if (dmarc.result === 'pass') {
trusted = true;
reason = 'dmarc_pass';
} else if (arc.result === 'pass' && dkim.some(s => s.result === 'pass')) {
// Forwarded mail with valid ARC and at least one passing DKIM
trusted = true;
reason = 'arc_backed_pass';
} else if (spf.result === 'pass' || dkim.some(s => s.result === 'pass')) {
// Soft accept path - mark for manual review
trusted = false;
reason = 'partial_authentication';
}
// Example: gate automatic ticket creation on trust
if (trusted) {
// createTicket(msg)
} else {
// queueForManualReview(msg)
}
console.log({
subject: msg.subject,
from: msg.from.address,
dmarc: dmarc.result,
spf: spf.result,
dkim: dkim.map(s => ({ domain: s.d, result: s.result })),
arc: arc.result,
decision: reason
});
res.status(200).json({ ok: true });
});
app.listen(3000, () => console.log('Webhook listening on 3000'));
Example B: Parsing Mandrill Inbound Authentication-Results
Mandrill Inbound posts a payload to your webhook containing an array of inbound events. The message structure includes headers and the raw MIME. Here is a simplified parser that extracts SPF, DKIM, and DMARC from the Authentication-Results header and computes a basic DMARC alignment decision.
// Node.js - Express example for Mandrill Inbound
const express = require('express');
const qs = require('querystring');
const app = express();
// Mandrill posts application/x-www-form-urlencoded by default
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
function parseAuthResults(arHeader) {
// Very simplified parser - production code should use a robust parser.
// Example AR: "mx.example.com; dkim=pass header.d=example.com; spf=pass smtp.mailfrom=example.com; dmarc=pass header.from=example.com"
const result = { spf: {}, dkim: [], dmarc: {} };
if (!arHeader) return result;
const parts = arHeader.split(';').map(p => p.trim());
parts.forEach(p => {
if (p.startsWith('spf=')) {
const m = p.match(/^spf=(\w+)(?:\s+smtp\.mailfrom=([^;\s]+))?/i);
result.spf.result = m ? m[1] : 'none';
result.spf.domain = m && m[2] ? m[2] : undefined;
} else if (p.startsWith('dkim=')) {
const m = p.match(/^dkim=(\w+)(?:\s+header\.d=([^;\s]+))?/i);
result.dkim.push({ result: m ? m[1] : 'none', d: m && m[2] ? m[2] : undefined });
} else if (p.startsWith('dmarc=')) {
const m = p.match(/^dmarc=(\w+)(?:\s+header\.from=([^;\s]+))?/i);
result.dmarc.result = m ? m[1] : 'none';
result.dmarc.aligned_identifier = m && m[2] ? m[2] : undefined;
}
});
return result;
}
function domainFromAddress(addr) {
const m = String(addr || '').match(/@([^>]+)>?$|@(.+)$/);
return m ? (m[1] || m[2]) : undefined;
}
function dmarcAligned(dmarcRes, fromDomain, spfRes, dkimRes) {
if (dmarcRes.result === 'pass') return true;
// Rough alignment check - production code should implement full r/s alignment rules.
const dkimAligned = dkimRes.some(s => s.result === 'pass' && s.d && fromDomain && fromDomain.endsWith(s.d));
const spfAligned = spfRes.result === 'pass' && spfRes.domain && fromDomain && fromDomain.endsWith(spfRes.domain);
return dkimAligned || spfAligned;
}
app.post('/mandrill-inbound', (req, res) => {
// Mandrill sends 'mandrill_events' as JSON string inside the form body
const events = JSON.parse(req.body.mandrill_events || '[]');
for (const ev of events) {
if (ev.event !== 'inbound') continue;
const msg = ev.msg || {};
const headers = msg.headers || {};
const arHeader = headers['Authentication-Results'] || headers['authentication-results'];
const auth = parseAuthResults(arHeader);
const fromDomain = domainFromAddress(msg.from_email || msg.from);
const trusted = dmarcAligned(auth.dmarc, fromDomain, auth.spf, auth.dkim);
console.log({
subject: msg.subject,
from: msg.from_email,
spf: auth.spf.result,
dkim: auth.dkim.map(s => ({ domain: s.d, result: s.result })),
dmarc: auth.dmarc.result,
decision: trusted ? 'trusted' : 'untrusted'
});
}
res.status(200).send('ok');
});
app.listen(4000, () => console.log('Mandrill inbound listener on 4000'));
Production systems should adopt a robust Authentication-Results parser that supports parameter lists, multiple occurrences, and RFC 7601 nuances. You will also want to implement r or s alignment rules for DMARC and handle internationalized domains via IDNA.
Performance and Reliability in Email Authentication
Authentication is only useful if it is fast, deterministic, and correct even when messages are messy. Here are the edge cases and how each platform addresses them:
- Multiple DKIM signatures - Messages can carry multiple DKIM-Signature headers, often from intermediaries or mailing lists. An inbound pipeline should evaluate each signature and return separate results. Mandrill Inbound relays all DKIM-Signature headers but does not evaluate them, so you must loop and verify logical pass conditions yourself. The structured approach evaluates and returns all signatures in auth.dkim.signatures[].
- Forwarding and SPF breakage - Forwarders typically fail SPF because the sending IP changes. DMARC allows DKIM alignment or ARC-backed trust to mitigate this. A structured pipeline can mark dmarc.result=pass due to aligned DKIM or arc.result=pass with cv=pass, while Mandrill requires you to parse and compute this logic from headers.
- Relaxed or simple canonicalization - DKIM signatures can use relaxed canonicalization, which tolerates minor header and whitespace changes. Correct evaluation means stable results even when MTAs fold long headers or change line endings. With Mandrill Inbound, relying on upstream Authentication-Results often works, but if you re-verify DKIM yourself you must implement canonicalization correctly.
- Authentication-Results multiplicity - Some messages include several Authentication-Results headers from different hops. You should pick the receiver's latest results or a trusted authserv-id. Mandrill Inbound provides all headers, so you must decide the correct one. A structured pipeline normalizes to the receiver's own authserv-id and exposes the final verdict.
- Non-ASCII domains and IDNA - Internationalized domains need punycode handling for SPF and DKIM domain comparisons. If you implement your own parser with Mandrill Inbound, ensure IDNA conversion both ways. A structured provider performs these conversions before computing alignment.
- Latency and throughput - Authentication checks should not bottleneck inbound throughput. Structured evaluation is executed at the MTA layer in parallel, then pushed downstream as fields. Mandrill's model pushes raw data quickly but shifts compute to your app tier, which can increase latency if you add DKIM and DMARC parsing in your webhook handler.
- Observability - You want to log the exact reasons for pass or fail to debug user reports. Structured results include reason codes, the aligned identifier, and which DKIM signature passed. With Mandrill Inbound, you can log the raw Authentication-Results but must also log your parser's interpretation for clarity.
For operations at scale, consider queuing inbound events before you perform heavy header parsing, setting time budgets for DMARC decisions, and implementing backoff for webhook retries. If you adopt a structured pipeline, most of that complexity is already handled, and your application can consume a small set of stable fields.
Verdict: Which Is Better for Email Authentication?
If your goal is to make fast, reliable trust decisions on inbound mail using SPF, DKIM, DMARC, and ARC, the platform that surfaces a normalized auth object in the webhook payload is easier to integrate and harder to misconfigure. You spend your engineering time on business logic instead of standards parsing.
Mandrill Inbound is a solid option if you already live in the Mailchimp ecosystem and prefer to parse Authentication-Results yourself. It forwards the necessary data and gets out of the way, but you will own the complexity of DMARC alignment and multi-signature DKIM handling.
For most teams that want a modern, developer-friendly experience with email-authentication baked in, MailParse is the better fit because it provides structured verdicts and per-mechanism details without extra parsing.
FAQ
What happens when SPF fails but DKIM passes - does DMARC still pass?
Often yes. DMARC passes when either SPF or DKIM passes with alignment to the visible From domain according to r or s mode. A common case is forwarded email where SPF fails, but an aligned DKIM signature still passes, so DMARC passes. Your webhook handler should rely on the DMARC verdict rather than a single mechanism.
How do mailing lists and forwarders affect email-authentication?
Mailing lists may modify subjects or footers, potentially breaking DKIM. Forwarders usually break SPF. DMARC is designed to navigate this through alignment and, increasingly, ARC. If you parse headers yourself with mandrill-inbound, implement logic that prefers aligned DKIM and honors ARC when present. In a structured pipeline, check auth.dmarc and auth.arc fields to decide whether to trust the message.
Should I trust the Authentication-Results header directly?
Only if it comes from a receiver you trust and you can identify the correct authserv-id. Some messages include multiple Authentication-Results headers from past hops. If you use Mandrill's inbound, pick the header generated by their receiving MTA. When your provider exposes normalized results, it already selects and validates the right header and transforms it into stable fields.
What is ARC and when should I use it?
ARC - the Authenticated Received Chain - carries a signed record of prior authentication results so downstream receivers can make informed decisions even when forwarding changes the message. Use ARC when you want to accept messages that break SPF due to forwarding but still have a trusted chain indicating a previous pass. Evaluate arc.result and cv together with DKIM outcomes.
Do I need raw MIME if I already get structured auth fields?
Yes, keep raw MIME for audits and forensics. Structured fields speed up decisions, but raw MIME lets you reproduce DKIM verification, inspect original headers, and defend against disputes. Store it securely and retain it according to your compliance needs.