Email Testing: MailParse vs Mailgun Inbound Routing

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

Introduction

Email testing is more than sending a message to a dummy inbox. Teams need disposable addresses they can spin up on demand, deterministic JSON that makes assertions easy, stable webhooks, and the ability to fetch raw MIME when an edge case breaks parsing. If your automated tests cover inbound workflows, the provider you choose will shape developer productivity, CI speed, and how quickly you can diagnose issues in production-like scenarios.

This topic comparison focuses specifically on email-testing capabilities for inbound workflows. We will look at how disposable addresses, sandbox isolation, structured JSON, raw MIME access, webhook verification, and replay options differ between the two approaches. If you are also designing or auditing your broader email stack, these resources pair well with the guidance below: Email Infrastructure Checklist for SaaS Platforms and Top Inbound Email Processing Ideas for SaaS Platforms.

How MailParse Handles Email Testing

MailParse is designed for developer-centric email testing. You can create disposable addresses instantly, receive fully parsed MIME as a normalized JSON document, and deliver results to a webhook or fetch them with a REST polling API. No DNS changes are required to start testing inbound flows.

Disposable test addresses

  • Instant provisioning of ephemeral inboxes for each test run, namespace, or developer branch.
  • Wildcard and plus-addressing support so a single test domain can cover many scenarios without configuration churn.
  • Optional TTLs so test inboxes expire automatically, keeping your environment clean.

Webhook delivery and verification

  • Webhook payload is structured JSON with consistent keys for headers, text and HTML bodies, attachments, inline images, and envelope metadata.
  • Request signing with HMAC, including a timestamp and unique event ID for idempotency and replay protection.
  • Secure retry with exponential backoff. Retries include the same event ID to simplify deduplication.

REST polling and raw MIME

  • A REST endpoint allows CI jobs to poll for messages by inbox ID or correlation ID, which is useful when your test runner cannot expose a public webhook.
  • Raw MIME is retained and available via a signed URL for deeper debugging, including DKIM verification, S/MIME, or malformed multipart structures.

Sample webhook JSON

{
  "id": "evt_01HZX8GQW5JQ9N0K5ZD8WJ9QZC",
  "received_at": "2026-04-25T14:21:37.982Z",
  "inbox_id": "ibx_dev_9f8a3a",
  "envelope": {
    "mail_from": "build@ci.example.com",
    "rcpt_to": ["orders+testcase123@in.test.example"]
  },
  "headers": {
    "subject": "Order #1234 confirmed",
    "from": "CI Bot <build@ci.example.com>",
    "to": "Orders <orders@in.test.example>",
    "message-id": "<abc123@example.com>"
  },
  "parts": {
    "text": "Thanks for your order...",
    "html": "<p>Thanks for your order...</p>"
  },
  "attachments": [
    {
      "filename": "invoice-1234.pdf",
      "content_type": "application/pdf",
      "size": 48213,
      "download_url": "https://files.example.test/att/4f2..."
    }
  ],
  "raw_mime_url": "https://files.example.test/mime/evt_01HZX8...",
  "signature": {
    "algo": "HMAC-SHA256",
    "timestamp": 1714054897,
    "nonce": "uLG1m0h8",
    "sig": "8b0f8c9c4b...a2f"
  }
}

The emphasis is on predictable structure for easy assertions. For example, the presence of parts.html, the exact header casing policy, and attachment metadata are consistent across messages so your tests do not require provider-specific branching.

How Mailgun Inbound Routing Handles Email Testing

Mailgun Inbound Routing is built around routes attached to verified domains. You create a route that matches incoming recipients, then forward content to your webhook or store the message for retrieval. For email testing, this offers flexibility but usually requires domain setup before you can run automated suites.

Setup and addressing model

  • You must verify a domain or subdomain and point MX records to Mailgun. This is ideal for production and staging, but slows down first-time test setup.
  • To simulate disposable addresses, teams often use a catch-all route and encode test identifiers in the local part, for example qa+case123@in.example.com.
  • Mailgun's sandbox domain targets sending and is not intended for inbound testing.

Inbound payload and verification

  • When a route forwards to a webhook, Mailgun posts application/x-www-form-urlencoded with fields like sender, recipient, subject, body-plain, body-html, message-headers, and file uploads for attachments.
  • Each request includes timestamp, token, and signature for HMAC-SHA256 verification using your API key.
  • Alternatively, the store() route action saves the message and lets you fetch the stored MIME via the Messages API, which can be helpful for edge-case email-testing.

Testing considerations

  • Form-encoded payloads are workable, but test assertions typically need helpers to parse the message-headers JSON and handle attachments streamed as multipart file parts.
  • Route propagation is quick, but DNS and MX setup make ephemeral, per-branch test domains less practical.
  • Pricing can be expensive at scale compared to alternatives, and some teams report occasional variability in webhook delivery under bursty test loads. Mailgun retries failed deliveries, which is helpful, but can complicate idempotency logic.

Side-by-Side Comparison

Feature for email testing MailParse Mailgun Inbound Routing
Disposable addresses without DNS Yes, instant per-test inboxes No, requires verified domain and MX
Sandbox or isolated namespace Built-in sandboxed inbox pools Use dedicated test subdomains
Structured JSON payload Native JSON with stable keys Form-encoded body with JSON-like headers field
Raw MIME access Signed URL for each event Available via store() and Messages API
REST polling for CI Yes, message search by inbox or correlation ID Primarily webhook. Polling requires using stored messages API
Webhook signature and idempotency HMAC with timestamp, nonce, and event ID HMAC with timestamp and token
Attachment handling Metadata in JSON, signed download URLs Multipart file uploads in webhook, or fetch when stored
Plus-addressing and tags First class in routing and inbox provisioning Works via catch-all routes and pattern matching
Setup time for new test env Minutes, no DNS changes Hours to days if DNS and approvals are needed
Cost and scaling for test runs Optimized for high-volume email-testing Can get expensive at scale
Webhook delivery behavior Event replay tools, consistent retries Retries supported, occasional variability reported

Code Examples

Creating a disposable test inbox and asserting a webhook

The following example shows a typical flow for developer-centric email testing: create a short-lived inbox, send a message to it from your system under test, then assert the received data in a webhook handler.

# 1) Create a disposable inbox
curl -X POST \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  https://api.example.test/v1/inboxes \
  -d '{
    "name": "ci-run-4281",
    "ttl_seconds": 3600,
    "tags": ["ci", "orders"]
  }'

# Response:
# {
#   "id": "ibx_dev_9f8a3a",
#   "address": "ci-run-4281@in.test.example",
#   "expires_at": "2026-04-25T15:21:37.000Z"
# }
// 2) Node.js Express webhook handler for parsed JSON
import crypto from "crypto";
import express from "express";

const app = express();
app.use(express.json({ type: "*/*" }));

function verifySignature(secret, payload, sig) {
  const hmac = crypto.createHmac("sha256", secret);
  hmac.update(payload.timestamp + ":" + payload.nonce + ":" + payload.id);
  const digest = hmac.digest("hex");
  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(sig));
}

app.post("/inbound-webhook", (req, res) => {
  const event = req.body;
  if (!verifySignature(process.env.SIGNING_SECRET, event, event.signature.sig)) {
    return res.status(401).send("bad signature");
  }

  // Assert key invariants for tests
  if (!event.headers.subject.includes("Order #")) {
    return res.status(400).send("missing subject");
  }
  if (!event.parts.text || !event.parts.html) {
    return res.status(400).send("expected both text and html");
  }

  // Idempotency
  // Use event.id to deduplicate if retried
  console.log("Inbound OK", event.id);
  res.status(200).send("ok");
});

app.listen(3000);
# 3) Poll if your CI cannot accept webhooks
curl -H "Authorization: Bearer <API_KEY>" \
  "https://api.example.test/v1/messages?inbox_id=ibx_dev_9f8a3a&limit=1"

Mailgun route and inbound handler for testing

Here is a basic route and Node.js handler for mailgun-inbound-routing. This uses a catch-all to forward any address at a subdomain to your webhook.

# Create a route that forwards to your webhook
curl -s --user 'api:<MAILGUN_API_KEY>' \
    https://api.mailgun.net/v3/routes \
    -F priority=0 \
    -F description='Catch-all for CI' \
    -F expression='match_recipient(".*@in.example.com")' \
    -F action='forward("https://ci.example.com/mg-inbound")' \
    -F action='stop()'
// Express handler that verifies Mailgun's signature
import crypto from "crypto";
import express from "express";
import multer from "multer";

const app = express();
const upload = multer();

function verifyMailgunSignature(apiKey, timestamp, token, signature) {
  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("/mg-inbound", upload.any(), (req, res) => {
  const { timestamp, token, signature } = req.body;
  if (!verifyMailgunSignature(process.env.MAILGUN_API_KEY, timestamp, token, signature)) {
    return res.status(401).send("bad signature");
  }

  const subject = req.body.subject || "";
  const text = req.body["body-plain"] || "";
  const html = req.body["body-html"] || "";

  // Attachments are multipart files in req.files
  // Example assertion in tests:
  if (!subject.includes("Order #")) return res.status(400).send("bad subject");

  res.status(200).send("ok");
});

app.listen(3001);

To work with raw MIME for detailed assertions, create a route with store(), then fetch the stored message from the Messages API using the URL provided in the event.

Performance and Reliability

For email testing, reliability is about determinism just as much as uptime. The difference shows up when CI triggers hundreds of parallel spec files that each send emails to unique addresses, then wait for inbound results.

Parsing consistency

  • Normalized JSON minimizes test flakiness. Key invariants include header casing rules, consistent attachment metadata, and stable body part selection when multiple alternatives are present.
  • Raw MIME access matters for edge cases like nested multiparts, large inline images, corrupted boundaries, DSNs, S/MIME, and PGP. Having both JSON and MIME makes debugging faster.

Throughput and latency

  • CI-friendly systems buffer bursts and keep per-message parse latency tight under load. Short-lived queues and event-level idempotency protect integrity during retries.
  • Mailgun Inbound Routing scales broadly, but teams occasionally observe seconds-level variation in webhook posting under bursts. Its retry logic is reliable, so make sure your tests are idempotent and treat duplicates as expected.

Operational ergonomics

  • Disposable addressing without DNS changes lets feature branches create their own test mailboxes. This shortens feedback cycles and reduces shared fixture contention.
  • When you operate your own domain for inbound, aligning MX records across environments can be a source of drift. A clear promotion path from test to staging to prod helps avoid surprises. For a full checklist of moving parts, see the Email Deliverability Checklist for SaaS Platforms.

Verdict: Which Is Better for Email Testing?

For teams that value instant disposable addresses, predictable JSON, CI polling, and raw MIME access without DNS chores, MailParse is the stronger fit for email-testing. You get a workflow built for automated assertions that scales from local runs to large parallel CI.

Mailgun Inbound Routing fits well if your organization already uses Mailgun extensively, you can tolerate the initial domain setup, and your tests are comfortable consuming form-encoded payloads or working with the stored Messages API. Keep in mind the potential cost at scale and plan for idempotent webhook handling to mitigate variability.

If you are evaluating long term architecture choices, also review the Top Email Parsing API Ideas for SaaS Platforms to map testing needs to production features like audit logging, encryption, retention, and observability.

FAQ

Do I need to buy and verify a domain to start inbound email testing?

Not necessarily. Some providers host disposable addresses on their own domains so you can test immediately. With Mailgun Inbound Routing, you will need to verify a domain or subdomain and point MX records before messages can be received.

How can I assert headers, inline images, and attachments reliably in CI?

Use a provider that returns a consistent JSON payload with preserved headers and attachment metadata. For inline images, prefer a content-id map so you can cross check references in HTML. When you need ground truth, fetch the raw MIME and run library-level assertions for DKIM, boundary integrity, or charset handling.

What is the best way to handle webhook retries and duplicate deliveries?

Trust but verify. Require HMAC signatures, then treat each inbound event as idempotent using a unique event identifier. Store processed IDs for a short TTL so duplicates are harmless. This keeps tests deterministic even when the provider retries due to transient network errors.

Our CI environment cannot expose a public URL. Can we still test inbound email?

Yes. Either use a tunneling tool during tests or prefer a provider with a REST polling API. Polling by inbox ID or correlation ID lets your tests fetch results synchronously without needing ingress.

Can I isolate test data to avoid leaking PII?

Use short-lived inboxes with explicit TTLs, encrypt stored messages at rest, and avoid forwarding test emails to shared inboxes. For compliance-heavy scenarios, rely on raw MIME links that expire and rotate signing keys regularly.

Ready to get started?

Start parsing inbound emails with MailParse today.

Get Started Free