Inbound Email Processing: MailParse vs Mailgun Inbound Routing

Compare MailParse and Mailgun Inbound Routing for Inbound Email Processing. Features, performance, and developer experience.

Why inbound email processing is a core capability for modern SaaS

For most platforms, inbound email processing is not a nice-to-have. It is the backbone for support ticketing, account replies, file intake, automated workflows, and user-to-user communication. When developers evaluate an email parsing service, the questions go far beyond "does it receive messages." You need control over receiving, routing, and processing. You need accurate MIME parsing into structured JSON, robust webhooks with verifiable signatures, replay or polling when your API is down, and predictable behavior across malformed messages, large attachments, and odd encodings.

This article compares two approaches to inbound-email-processing: a developer-focused parsing pipeline that emphasizes structured JSON and replay options, and Mailgun Inbound Routing, which emphasizes routing rules and multipart webhook payloads. We focus on the specifics of receiving, routing, and processing inbound messages programmatically via API, so you can choose an approach that fits your architecture.

If you are planning your broader email stack, complement this comparison with the following resources:

How MailParse handles inbound email processing

MailParse focuses on turning raw SMTP into predictable JSON, so application code handles a single schema regardless of client quirks. It provides instant email addresses, normalizes and parses MIME, then delivers via webhook or a REST polling API. The design encourages idempotent processing, message replay, and clear failure semantics.

Provisioning addresses and routing

  • Create ad-hoc addresses or inboxes instantly using an API. Addresses can be per-user, per-tenant, or per-conversation, making correlation simple.
  • Define routing rules using recipient domains, local parts, or header matches. Rules emit to one or more webhooks, or to a queue for later polling.
  • Catch-all and wildcard support simplify development environments and ephemeral testing.

Webhook delivery and verification

Inbound deliveries post a single JSON payload to your endpoint. A 2xx response acknowledges receipt. Non-2xx responses trigger exponential backoff with jitter, with a bounded attempt window and dead-lettering for recovery via API. Each request includes a timestamp and HMAC signature header you can verify with your API secret.

// Example Express handler that verifies a signed inbound webhook
import crypto from 'crypto';
import express from 'express';

const app = express();
// Raw body or a verified JSON body parser is recommended
app.use(express.json({ limit: '25mb' }));

function verifySignature({ timestamp, signature, body }, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(timestamp + '.' + JSON.stringify(body));
  const expected = hmac.digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

app.post('/inbound/webhook', (req, res) => {
  const timestamp = req.get('X-Timestamp');
  const signature = req.get('X-Signature');
  if (!verifySignature({ timestamp, signature, body: req.body }, process.env.INBOUND_SECRET)) {
    return res.status(401).send('invalid signature');
  }

  const msg = req.body;
  // Idempotency - dedupe using provider messageId or an event id
  // storeEventIfNew(msg.eventId);

  // Process the normalized JSON
  // msg.text, msg.html, msg.attachments[], msg.headers, msg.from, msg.to, msg.subject...
  res.status(204).end();
});

app.listen(3000);

Normalized JSON schema

The inbound JSON is designed for direct consumption:

  • Top-level fields: messageId, receivedAt, envelope.from, from[], to[], cc[], bcc[], subject, inReplyTo, references[], headers (map), tags[], spamVerdict, dkim, spf.
  • Bodies: text and html are normalized to UTF-8 with a consistent newline convention. Quoted replies and signatures can be optionally stripped.
  • Attachments: filename, contentType, size, contentId, isInline, hash, and either a temporary URL or base64 content. Inline images are indicated with isInline, and a contentId map is included for resolving cid: links.
  • Raw MIME: optionally included as base64 if you require a reparse or signature audits.
  • Routing metadata: which rule matched, original recipient, and any custom variables passed from your route.

Polling API and replay

In addition to webhooks, a REST API lets you poll for new messages or replay previously delivered messages. Use this when rolling out new webhooks, during maintenance windows, or to backfill. Responses include opaque event ids so you can request exactly-once processing and audit trails.

# Fetch unacknowledged messages for a tenant or inbox
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.example.test/v1/inbound/messages?inbox=accounts&status=pending&limit=50"

# Acknowledge processing to prevent redelivery
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"eventIds":["evt_123","evt_456"]}' \
  "https://api.example.test/v1/inbound/ack"

Error handling and idempotency

  • 2xx responses complete delivery. 4xx responses are treated as permanent for that attempt and may dead-letter depending on code and retries. 5xx responses trigger retries with backoff.
  • Each webhook contains a stable event id and the underlying Message-Id, so you can dedupe across restarts.
  • Timeouts are tuned for typical compute platforms. If your endpoint cannot respond quickly, you can use the polling API and move complex work off the webhook path.

How Mailgun Inbound Routing handles inbound email processing

Mailgun Inbound Routing centers on configurable routes and multipart forwarding. You define match expressions and actions that forward messages to your HTTP endpoint, store them, or stop processing. Parsing is performed by Mailgun, and the result is posted as multipart/form-data with message fields and attachments as file parts.

Routing with expressions

Inbound routing rules are created via the Routes API or dashboard using matchers and actions. Common expressions include recipient and header matches, with priority and stop rules:

# Create a route that forwards support mail to your webhook
curl -s -u "api:$MAILGUN_API_KEY" \
  https://api.mailgun.net/v3/routes \
  -F priority=1 \
  -F description='Support inbox' \
  -F expression='match_recipient("support@yourdomain.com")' \
  -F action='forward("https://api.yourapp.com/inbound/mailgun")' \
  -F action='stop()'

Webhook payload format and verification

When a route forwards to your endpoint, the payload includes fields like from, to, subject, body-plain, body-html, stripped-text, stripped-signature, and attachments as uploaded files. Verification uses a timestamp, token, and signature you compute with your API key. You can also choose a store action and read the raw MIME from a URL in subsequent requests.

// Express handler verifying Mailgun signature on a multipart payload
import express from 'express';
import crypto from 'crypto';
import multer from 'multer';

const upload = multer({ limits: { fileSize: 25 * 1024 * 1024 } }); // adjust as needed
const app = express();

function verifyMailgunSig(timestamp, token, signature, apiKey) {
  const hmac = crypto.createHmac('sha256', apiKey);
  hmac.update(timestamp + token);
  const digest = hmac.digest('hex');
  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
}

app.post('/inbound/mailgun', upload.any(), (req, res) => {
  const { timestamp, token, signature } = req.body;
  if (!verifyMailgunSig(timestamp, token, signature, process.env.MAILGUN_SIGNING_KEY)) {
    return res.status(401).send('invalid signature');
  }

  const fields = req.body;      // from, to, subject, body-plain, body-html, etc.
  const files = req.files || []; // attachments as file uploads
  // Process fields and files...
  res.status(204).end();
});

app.listen(3000);

Delivery semantics and limits

  • Exponential backoff on failures with route-level management in the control plane.
  • Attachments are uploaded as multipart parts, which is convenient for streaming to object storage.
  • For raw MIME, use the store action and fetch the message via a provided URL.
  • Routes have quotas and limits by plan. Some customers note cost growth at scale, especially with many routes or higher inbound volumes.

Side-by-side comparison for inbound-email-processing

Capability MailParse Mailgun Inbound Routing
Payload format Single JSON with normalized text, html, headers map, attachments metadata, optional raw MIME Multipart form-data fields like body-plain, body-html, attachments as file uploads, optional stored raw MIME
Signature verification HMAC SHA-256 over timestamp + body, secret-scoped per project HMAC SHA-256 over timestamp + token with account signing key
Replay and polling Native REST polling and replay with event ids for exactly-once semantics Store action and Events API, or refetch via message URL when using store
Address provisioning Instant per-tenant or per-conversation addresses via API Addresses managed at your domain - leverage routes to forward
Routing rules Recipient and header matches, rule metadata in payload, multi-endpoint fanout match_recipient, match_header, priority and stop actions
Attachment handling Metadata-rich entries with download URLs or base64 content for small files Streaming-friendly multipart file uploads appropriate for direct S3 writes
Parsing fidelity Normalized Unicode, consistent newlines, contentId map, inline asset resolution Provides body-plain, body-html, stripped fields, content-id map exposed via attachments
Error handling 2xx ack, configurable backoff, dead-letter with API-driven recovery Exponential backoff, potential route disablement on sustained failures
Reliability at scale Webhook delivery plus polling provides resilience in outages Reliable for many workloads, though some teams report occasional webhook delivery variability
Cost profile Optimized for high-volume JSON delivery Powerful routes - costs can rise with volume and number of routes

Code examples: developer experience compared

Creating an inbound address and receiving a message via JSON

# Create an inbox or address programmatically
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "support-inbox",
    "routing": [
      {"match": {"recipient": "support@acme.example"}, "action": {"webhook": "https://api.acme.com/inbound/support"}}
    ]
  }' \
  "https://api.example.test/v1/inboxes"

# Example inbound JSON received at your webhook
{
  "eventId": "evt_01HX5...",
  "messageId": "<CA+Zx...@mail>",
  "receivedAt": "2026-04-21T14:33:12Z",
  "envelope": {"from": "bounce.mail@example.net", "to": ["support@acme.example"]},
  "from": [{"name":"Jane Roe","address":"jane@example.com"}],
  "to": [{"address":"support@acme.example"}],
  "subject": "Re: Invoice #2024-1172",
  "headers": {"Message-Id":"<...>","References":"<...>"},
  "text": "Hi team,\nPlease see the attached PDF.\n",
  "html": "<p>Hi team,</p><p>Please see the attached PDF.</p>",
  "attachments": [
    {"id":"att_123","filename":"invoice.pdf","contentType":"application/pdf","size":182337,"isInline":false,"url":"https://files.example.test/att_123?sig=..."}
  ],
  "dkim": {"result":"pass"},
  "spf": {"result":"pass"},
  "spamVerdict": {"score":0.2,"action":"accept"},
  "route": {"name":"support-inbox","matched":"recipient"}
}

Configuring Mailgun route and handling multipart payload

# Match help desk emails and forward plus store raw MIME
curl -s -u "api:$MAILGUN_API_KEY" \
  https://api.mailgun.net/v3/routes \
  -F priority=1 \
  -F description='Helpdesk' \
  -F expression='match_recipient("help@yourdomain.com")' \
  -F action='store()' \
  -F action='forward("https://api.yourapp.com/inbound/mailgun")' \
  -F action='stop()'
// Node handler snippet - access fields and stream attachments
app.post('/inbound/mailgun', upload.any(), async (req, res) => {
  // Verification omitted here for brevity - see earlier example
  const { 'body-plain': text, 'body-html': html, subject, from, to } = req.body;
  for (const file of req.files) {
    // file.buffer or file.stream depending on Multer storage
  }
  res.status(204).end();
});

Performance and reliability in real-world inbound workflows

Handling large or malformed messages

  • Large attachments: Stream to object storage. With multipart payloads, you can pipe directly from the request to S3 or GCS. With JSON + signed URLs, download asynchronously after acknowledging the webhook.
  • Malformed or exotic MIME: Favor providers that expose raw MIME for replay and give you a consistent structure after normalization. This avoids surprises from odd charsets or nested multiparts.
  • Inline images and cid references: Ensure the payload includes a contentId map and inline flags so your renderer or sanitizer can resolve images reliably.

Backpressure and outages

  • Use idempotent handlers - acknowledge quickly, enqueue work to a durable queue, and process asynchronously. This keeps your webhook fast and minimizes retries.
  • Rely on replay or polling when your API is down or during deploys. A polling API provides a secondary path that many teams use while blue/green deployments settle.
  • Design for retries: keep handlers stateless, dedupe using eventIds or Message-Ids, and write side effects after you persist the inbound event.

Webhook variability and costs

  • Some teams report occasional variability in webhook delivery timing with mailgun-inbound-routing under heavy load or during endpoint instability. Mitigate this with robust retries, monitoring, and route configuration.
  • At scale, multipart uploads can increase bandwidth and processing costs. Conversely, repeatedly downloading large attachments from temporary URLs can also incur egress. Profile both patterns in your workload.
  • Route limits, per-message pricing, and extra features can compound costs in Mailgun's ecosystem. Budget for growth and favor predictable delivery semantics.

Verdict: which is better for inbound email processing

If your application needs structured JSON with consistent MIME parsing, simple signature verification, and a built-in replay or polling path, MailParse provides a streamlined developer experience that is easy to reason about under scale. The normalized schema and dual delivery modes reduce operational surprises and simplify testing.

If you are deeply invested in Mailgun's outbound infrastructure, prefer multipart uploads for direct-to-storage streams, and like the match_recipient and match_header routing grammar, Mailgun Inbound Routing is a solid option. Be mindful of plan limits and watch webhook behavior under stress so retries and dead-letter paths are well tested.

In short: choose MailParse when you prioritize consistent JSON, replay-friendly delivery, and fast per-tenant address provisioning. Choose Mailgun Inbound Routing when you value route expressions and multipart uploads inside an existing Mailgun footprint.

FAQ

How do I verify inbound webhook signatures securely?

Use HMAC SHA-256 over a canonical string that includes a timestamp to prevent replay attacks. Validate that the timestamp is recent, recompute the HMAC using your signing secret, and compare using a timing-safe comparison. Reject requests with missing headers or stale timestamps. Rotate signing keys periodically and log verification failures with correlation ids.

What is the best way to handle large attachments in inbound-email-processing?

Do not fully buffer large files in memory. For multipart payloads, stream attachments directly to object storage and persist only metadata. For JSON payloads with signed URLs, acknowledge the webhook quickly, then fetch attachments asynchronously from background workers. In both cases, enforce size limits, compute hashes for integrity, and sanitize filenames.

Should I use webhooks or polling for receiving inbound messages?

Use webhooks as the primary path for low latency and efficiency. Add a polling or replay path for resilience and backfills. Many teams acknowledge webhooks quickly, enqueue processing, then use polling to reconcile gaps after incidents or deploys. The combination yields the best reliability profile.

Do I need raw MIME if I already receive parsed text and html?

Not always. Parsed text and html are sufficient for most workflows. Raw MIME helps with signature validation, proprietary multipart structures, or when you want to reparse with custom heuristics. If you enable raw MIME access, store it briefly and scrub PII according to your data policies.

How can I keep inbound email processing reliable during deploys?

Design for quick 2xx acknowledgments, use circuit breakers, and fail closed on signature mismatches. Deploy behind a load balancer with health checks, and keep queues draining. If your provider supports replay or polling, pause nonessential processing during a deploy and resume with a backfill pass. Monitor 5xx rates, retry counts, and time-to-ack as first-class SLOs.

For a broader checklist that pairs well with inbound processing, see the Email Deliverability Checklist for SaaS Platforms.

Ready to get started?

Start parsing inbound emails with MailParse today.

Get Started Free