Why email authentication should drive your inbound parsing choice
Email authentication is not only for outbound deliverability. On the inbound side, your application needs to know who actually sent a message and whether anything was altered in transit. That means parsing and validating SPF, DKIM, and DMARC, then exposing the results in a way that is easy to consume in code. If your inbound email parsing service handles authentication poorly, your app may accept spoofed or manipulated messages, misroute tickets, or create security exposures.
Strong email-authentication support turns every incoming message into a structured, signed event your platform can trust. In practice, that requires:
- Validating SPF against the SMTP envelope, not just the
From:header, and surfacing which identity passed or failed. - Verifying all DKIM signatures, not only the first one, and reporting per-signature results and selectors.
- Evaluating DMARC alignment against the authenticated identifier and the visible From domain, with policy awareness.
- Preserving the full
Authentication-Resultsheader and raw MIME so you can audit and re-check later. - Handling real-world edge cases like forwarded mail, multiple signatures, and volatile DNS.
If you are designing a SaaS feature that depends on trusted inbound mail, the way a platform implements these checks will determine your security posture and how much glue code you need to write. The sections below compare how a modern JSON-first service and Twilio SendGrid Inbound Parse approach authentication, where each is strong, and what it takes to ship production-grade validations.
Related reading for your stack: Email Infrastructure Checklist for SaaS Platforms and Top Inbound Email Processing Ideas for SaaS Platforms.
How MailParse Handles Email Authentication
MailParse (MP) validates SPF, DKIM, and DMARC on every inbound message and returns structured JSON that captures both the verdicts and the underlying evidence. The goal is to let your application make an informed decision without scraping headers or building an email-auth stack from scratch.
SPF validation
- Checks SPF per RFC 7208 against the SMTP MAIL FROM identity, with a HELO fallback when the envelope sender is empty.
- Captures verdicts like
pass,fail,softfail,neutral,none,temperror, andpermerror. - Includes the evaluated domain, connecting IP, scope, and the mechanism that produced the result.
DKIM verification
- Validates every DKIM signature it finds, not just the first one.
- Returns an array of signature objects with
selector,domain,result,canon, and body hash status. - Handles large DNS keys, CNAME indirection, and multiple headers in canonicalization.
DMARC evaluation and alignment
- Evaluates DMARC per RFC 7489, including alignment mode (
relaxedorstrict), organizational domain calculation, and subdomain policy. - Reports whether alignment passed via SPF or DKIM, and exposes policy metadata such as
p,sp, andpct. - Surfaces the platform's DMARC decision alongside the underlying SPF and DKIM outcomes.
ARC and header preservation
- Validates ARC chains when present and exposes the chain length and trust decision. This helps your app accept properly authenticated mail that traversed a forwarder or list.
- Preserves the original
Authentication-Results,Received, and all other headers in both structured form and raw MIME so you can audit later.
Webhook and polling payloads
By default, MP delivers a JSON payload to your webhook or via REST polling with a schema that includes:
{
"id": "evt_01HX...",
"received_at": "2026-04-24T15:02:12Z",
"smtp": {
"mail_from": "bounce@example.net",
"helo": "mx.example.net",
"remote_ip": "203.0.113.9"
},
"auth": {
"spf": { "result": "pass", "domain": "example.net", "scope": "mfrom", "mechanism": "ip4", "ip": "203.0.113.9" },
"dkim": [
{ "result": "pass", "domain": "example.org", "selector": "s1", "canon": "relaxed/relaxed" }
],
"dmarc": {
"result": "pass",
"aligned_via": "dkim",
"domain": "example.org",
"policy": { "p": "quarantine", "sp": "none", "adkim": "r", "aspf": "r", "pct": 100 }
},
"arc": { "validated": true, "chain": 2 }
},
"headers": { "...": "..." },
"raw": "RFC 5322 message bytes"
}
This structure avoids header scraping and encourages consistent handling of email authentication across all of your pipelines.
How SendGrid Inbound Parse Handles Email Authentication
Twilio SendGrid Inbound Parse receives mail at SendGrid MX records and forwards the message contents to your webhook. The service posts a multipart/form-data payload with convenience fields like from, to, subject, html, and text. For authentication, sendgrid's payload can include dkim, SPF, spam, spam_score, spam_report, sender_ip, and envelope. When the Post the raw, full message
setting is enabled, the request also contains the full RFC message in an email field, and you can parse Authentication-Results yourself.
Important details for teams focused on email-authentication:
- SPF and DKIM: The
SPFanddkimfields provide basic pass or fail indicators. They are sufficient for simple checks, but do not enumerate multiple DKIM signatures with per-signature metadata. - DMARC: The webhook does not provide a native DMARC verdict or alignment details. You can compute DMARC by combining the visible From domain with SPF and DKIM outcomes taken from headers or the convenience fields, or by parsing the raw message.
- ARC: There is no first-class ARC evaluation. If ARC matters for forwarded messages, you need to verify it from the raw headers using your own library.
- Transport: The form-data payload is widely compatible, but increases the amount of parsing code you write for authentication use cases compared to JSON.
- Setup: sendgrid-inbound-parse requires pointing your domain's MX to mx.sendgrid.net. If your inbound flow needs to live outside of the SendGrid ecosystem, that adds operational constraints.
In short, SendGrid Inbound Parse is reliable for receiving and forwarding email content, it exposes basic SPF and DKIM signals, and it offers raw MIME on demand. Teams that need DMARC and ARC decisions will implement those themselves. If you are already standardized on Twilio's stack and only need pass or fail flags, it remains a pragmatic choice.
Side-by-side authentication feature comparison
| Feature | MP | SendGrid Inbound Parse |
|---|---|---|
| SPF result with evaluated domain and scope | Yes, full RFC 7208 results with mechanism and scope | Basic SPF field with pass or fail |
| HELO fallback when MAIL FROM is empty | Yes, surfaced in payload | Not explicitly surfaced |
| DKIM multi-signature reporting | Yes, array of signatures with selectors and canonicalization | Single dkim field, multiple signatures not enumerated |
| DMARC verdict and alignment | Yes, alignment and policy metadata | No native verdict, compute yourself from headers |
| ARC chain evaluation | Yes, chain length and validity | No built-in evaluation |
| Authentication-Results preservation | Yes, structured and raw | Raw header available if you enable full message posting |
| Raw MIME access | Always available in payload | Available when Post the raw, full messageis enabled |
| Webhook transport | JSON with typed authentication objects | Multipart form-data |
| Webhook signing | HMAC signature header for verification | Optional request signing via Twilio security features |
| Forwarding resilience via ARC | Yes, evaluates ARC to accept authenticated forwards | Requires custom ARC verification |
Code examples
MP webhook handler that enforces DMARC
The example below accepts a JSON payload, verifies the HMAC signature, then applies a policy that requires DMARC pass or an ARC-validated forward. Replace the secret and tune to your needs.
// Node.js + Express
const express = require("express");
const crypto = require("crypto");
const app = express();
app.use(express.json({ limit: "10mb" }));
function verifySignature(req, body, secret) {
const sig = req.get("X-MP-Signature"); // base64 HMAC-SHA256
const hmac = crypto.createHmac("sha256", secret).update(body).digest("base64");
return crypto.timingSafeEqual(Buffer.from(sig || "", "base64"), Buffer.from(hmac, "base64"));
}
app.post("/webhooks/inbound", (req, res) => {
const rawBody = JSON.stringify(req.body);
if (!verifySignature(req, rawBody, process.env.MP_WEBHOOK_SECRET)) {
return res.status(401).send("invalid signature");
}
const auth = req.body.auth || {};
const dmarc = auth.dmarc || {};
const arc = auth.arc || {};
// Accept if DMARC passed or if ARC chain validated and SPF or DKIM passed upstream
const dkimPassed = (auth.dkim || []).some(sig => sig.result === "pass");
const spfPassed = (auth.spf && auth.spf.result === "pass");
const accept =
dmarc.result === "pass" ||
(arc.validated === true && (dkimPassed || spfPassed));
if (!accept) {
// Quarantine or flag for manual review
console.warn("Auth failure", { id: req.body.id, auth });
return res.status(202).send("quarantined");
}
// Process the message
// ...
return res.status(200).send("ok");
});
app.listen(3000);
SendGrid Inbound Parse handler with DIY DMARC alignment
This example parses the form-data payload, reads the SPF and dkim fields, and evaluates a simplified DMARC alignment against the visible From domain. If you enable posting of the raw message, you can parse full headers from req.body.email for better fidelity.
// Node.js + Express with form-data parsing
const express = require("express");
const multer = require("multer");
const app = express();
const upload = multer();
function domainFromAddress(addr) {
const match = /@([^>]+)>?$/.exec(addr || "");
return match ? match[1].trim().toLowerCase() : "";
}
function relaxedAlign(child, parent) {
// Very simplified org-domain check. Use a real PSL-based library in production.
return child === parent || child.endsWith("." + parent);
}
app.post("/sendgrid/inbound", upload.any(), (req, res) => {
// Typical fields: req.body.from, req.body.to, req.body.SPF, req.body.dkim, req.body.email
const visibleFrom = (req.body.from || "").toString();
const fromDomain = domainFromAddress(visibleFrom);
const spfField = (req.body.SPF || "").toString().to