Email Parsing API: MailParse vs CloudMailin

Compare MailParse and CloudMailin for Email Parsing API. Features, performance, and developer experience.

Why Email Parsing API Capabilities Matter

For many SaaS platforms, inbound email is not just a communication channel, it is a data pipeline. Support replies, customer uploads, automated reports, and user-generated content often arrive as raw emails with complex MIME structures. A robust email parsing API turns those raw messages into clean, structured JSON that your services can trust. The difference between a great and a merely adequate implementation shows up in edge cases: nested multiparts, odd charsets, inline images, calendar invites, and winmail.dat attachments from legacy clients.

This comparison focuses on the email parsing API capabilities of two cloud-based inbound email providers. It looks at REST and webhook delivery, JSON schema stability, signature verification, idempotency, and how each platform handles tricky MIME content. If you are evaluating integration patterns for a new product, you may also find these resources useful: Top Email Parsing API Ideas for SaaS Platforms and Email Infrastructure Checklist for SaaS Platforms.

MailParse targets developers who want instant inboxes, reliable MIME to JSON parsing, and delivery via webhook or REST polling. CloudMailin focuses primarily on webhook-based delivery of inbound messages, with optional storage to retrieve the raw message or attachments.

How MailParse Handles Email Parsing API

This service provides two integration modes so teams can pick the best fit for their architecture: push via webhook or pull via REST. In both cases, the platform receives the message at an instant email address or a domain you route, parses MIME deterministically, and exposes a structured JSON document with consistent field names and normalized encodings.

Webhook model

  • Delivery: HTTPS POST of a fully parsed JSON payload to your endpoint.
  • Security: HMAC signature with timestamp to prevent tampering and replay. You verify a header like X-Signature: t=<unix>,v1=<hmac-sha256>.
  • Retries: Exponential backoff on non-2xx responses, with a maximum time-to-live so your queue does not grow unbounded.
  • Idempotency: A unique message ID and delivery ID appear in headers so you can deduplicate easily.
  • Attachment delivery: Each attachment includes metadata and a short-lived signed URL so your app can download on demand without embedding large base64 blobs.
{
  "id": "msg_7Y9b2Q",
  "received_at": "2026-04-22T12:34:56Z",
  "envelope": {
    "from": "alice@example.com",
    "to": ["support@acme.app"],
    "remote_ip": "203.0.113.10",
    "helo": "mail.example.com"
  },
  "headers": {
    "subject": "Invoice for April",
    "message-id": "<CA1234@example.com>",
    "in-reply-to": null,
    "references": []
  },
  "subject": "Invoice for April",
  "from": [{"name": "Alice", "address": "alice@example.com"}],
  "to": [{"name": "Support", "address": "support@acme.app"}],
  "cc": [],
  "reply_to": [],
  "list_unsubscribe": ["<mailto:unsubscribe@example.com>", "<https://example.com/u>"],
  "text": "Hello team,\nPlease see the attached invoice.\n",
  "html": "<p>Hello team,</p><p>Please see the attached invoice.</p>",
  "attachments": [{
    "id": "att_Lk8Nw",
    "filename": "invoice.pdf",
    "content_type": "application/pdf",
    "size": 48233,
    "is_inline": false,
    "sha256": "7c5f...f2a",
    "download_url": "https://files.example/att_Lk8Nw?sig=...&exp=1713792000"
  }],
  "inline": [{
    "cid": "image001.png@01D",
    "attachment_id": "att_img1"
  }],
  "authentication": {
    "spf": {"result": "pass"},
    "dkim": [{"domain": "example.com", "result": "pass"}],
    "dmarc": {"result": "pass"}
  },
  "spam": {"score": 0.1, "flagged": false},
  "charsets": {"text": "utf-8", "headers": "utf-8"},
  "warnings": []
}

REST polling

  • Endpoints to list, fetch, and acknowledge messages. Pagination and cursor-based iteration make it easy to backfill.
  • Idempotency keys for any modifying call. Retries from your side will not create duplicate state.
  • Attachment download via signed URLs with configurable lifetime. Optionally require token binding to your IP or a per-tenant secret.
  • Schema stability across versions. When fields evolve, the service uses additive changes and versioned endpoints.

Parsing details worth highlighting:

  • Strict MIME tree traversal. Each part is decoded according to its Content-Transfer-Encoding and charset with safe fallbacks.
  • Inline images are mapped by Content-ID to a stable attachment ID to simplify HTML rendering.
  • TNEF winmail.dat is detected and expanded into discrete attachments when possible, with the original binary also preserved.
  • Large message handling uses streaming so memory consumption remains predictable.

How CloudMailin Handles Email Parsing API

CloudMailin is a cloud-based inbound email processing service built primarily around webhooks. You configure an address or route, and the platform posts either the raw MIME or a parsed JSON payload to your HTTP endpoint. Developers can choose between:

  • Parsed JSON delivery, which includes envelope, headers, plain and HTML bodies, and an attachments array. Attachments can be included as base64 or exposed via a link when storage is enabled.
  • Raw message delivery so you can run your own MIME parser if you need specialized treatment.

Common webhook fields include envelope (from, to, recipients), headers, plain, html, and attachments entries containing filename, content type, size, disposition, and optionally base64 content or a reference URL. The service supports HTTPS signatures so your application can verify authenticity, typically through an HMAC computed over the request body and shared token, delivered in a header such as X-CloudMailin-Signature.

When CloudMailin storage is enabled, you can retrieve messages or attachments later through their REST interface. This is useful if you want to avoid receiving large base64 blobs in your webhook payload and instead fetch on demand. Developers also benefit from configurable routes, per-address settings, and the option to accept or reject messages at the SMTP stage based on custom logic.

Side-by-Side Comparison: Email Parsing API Features

Feature MailParse CloudMailin
Webhook delivery of parsed JSON Yes, deterministic schema with normalized charsets Yes, JSON payload with envelope, headers, body, attachments
Optional raw MIME delivery Yes, include raw alongside parsed or fetch via REST Yes, raw delivery supported
REST polling for messages Yes, first class list, fetch, ack, and redelivery endpoints Available when storage is enabled, otherwise webhook-first
Webhook signature verification HMAC with timestamp and versioned scheme HMAC signature header such as X-CloudMailin-Signature
Idempotency and retry controls Idempotency keys and delivery IDs for deduplication Webhook retries on failure, dedupe via your application logic
Attachment handling Signed URLs, metadata, streaming download Base64 in payload or link-based when storage is configured
Inline image CID mapping Yes, explicit mapping from CID to attachment ID Present when attachments are parsed, verify disposition and CID
Charset and encoding normalization Yes, consistent UTF-8 surfaces with original preserved Yes for common charsets, raw delivery available for custom parsing
TNEF winmail.dat extraction Extracted into discrete files, raw preserved Raw TNEF preserved, extraction typically handled by your code
JSON schema versioning Versioned schemas with additive evolution Stable payload with documented fields, raw option for full control
Ecosystem and integrations Broad examples, SDKs, and integrations Smaller ecosystem, solid docs and examples

Code Examples: Developer Experience With Each Platform

Webhook handler with signature verification

Node.js example verifying a timestamped HMAC signature and parsing the message:

// npm install express crypto body-parser
const express = require('express');
const crypto = require('crypto');
const app = express();

// Capture raw body for signature verification
app.use(express.json({
  verify: (req, res, buf) => { req.rawBody = buf; }
}));

// Secret you configure in the provider dashboard
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

function verifySignature(signatureHeader, rawBody) {
  // Example header: "t=1713792000,v1=abcdef..."
  const parts = Object.fromEntries(signatureHeader.split(',').map(kv => kv.split('=')));
  const ts = parts.t;
  const v1 = parts.v1;

  const payload = `${ts}.${rawBody}`;
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET).update(payload).digest('hex');

  // Constant time comparison
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(hmac));
}

app.post('/inbound/webhook', (req, res) => {
  const sig = req.get('X-Signature');
  if (!sig || !verifySignature(sig, req.rawBody)) {
    return res.status(400).send('Invalid signature');
  }

  const msg = req.body;

  // Basic normalization example: pick HTML if available, else text
  const body = msg.html || `<pre>${msg.text || ''}</pre>`;

  // Save message metadata
  console.log('Message ID:', msg.id);
  console.log('Subject:', msg.subject);
  console.log('From:', (msg.from || []).map(f => f.address).join(', '));

  // Download attachments as needed
  for (const a of msg.attachments || []) {
    console.log(`Attachment: ${a.filename} (${a.content_type}) size=${a.size}`);
    // Fetch a.download_url with your HTTP client if you need the file
  }

  // Always return 2xx to stop retries when processing succeeds
  res.status(204).send();
});

app.listen(3000, () => console.log('Webhook server listening on :3000'));

CloudMailin webhook verification

CloudMailin posts a JSON payload to your endpoint and includes an HMAC signature header such as X-CloudMailin-Signature. A minimal verification approach looks like this:

const express = require('express');
const crypto = require('crypto');
const app = express();

// Collect raw body
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));

const CLOUDMAILIN_TOKEN = process.env.CLOUDMAILIN_TOKEN;

function verifyCloudMailin(sigHeader, rawBody) {
  // Many implementations use hex digest of HMAC-SHA256 over the body with your token
  const expected = crypto.createHmac('sha256', CLOUDMAILIN_TOKEN)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sigHeader));
}

app.post('/cloudmailin/inbound', (req, res) => {
  const sig = req.get('X-CloudMailin-Signature');
  if (!sig || !verifyCloudMailin(sig, req.rawBody)) {
    return res.status(400).send('Invalid signature');
  }

  const payload = req.body;
  const subject = payload.headers && payload.headers.Subject;
  const plain = payload.plain || '';
  const html = payload.html || '';

  // Attachments may be base64 in payload or references if storage is enabled
  for (const a of payload.attachments || []) {
    console.log(`Attachment: ${a.file_name} (${a.content_type}) size=${a.size}`);
  }

  res.status(204).send();
});

app.listen(3001, () => console.log('CloudMailin webhook listening on :3001'));

Polling API example

If you prefer a pull model for operational control or firewalled environments, use REST polling. The sequence is list, fetch, process, acknowledge. Here is a simple cURL and Python sketch:

# List ready messages
curl -s -H "Authorization: Bearer $API_TOKEN" \
  "https://api.example.email/v1/messages?status=ready&limit=50"

# Fetch a single message
curl -s -H "Authorization: Bearer $API_TOKEN" \
  "https://api.example.email/v1/messages/msg_7Y9b2Q"
import os, requests

base = "https://api.example.email/v1"
headers = {"Authorization": f"Bearer {os.environ['API_TOKEN']}"}

# Get next page of messages
resp = requests.get(f"{base}/messages", params={"status": "ready", "limit": 25}, headers=headers)
for m in resp.json()["data"]:
    # Process message
    print(m["id"], m["subject"])
    for a in m.get("attachments", []):
        file = requests.get(a["download_url"]).content
        # Save or stream to storage here

    # Acknowledge so it is not delivered again
    requests.post(f"{base}/messages/{m['id']}/ack", headers=headers)

Performance and Reliability

Inbound email is unpredictable. Systems need to parse, normalize, and deliver data reliably when the content is deeply nested or slightly malformed. Here is how the two services handle demanding scenarios that matter for an email-parsing-api.

Large messages and backpressure

  • Webhook-first push is efficient but can overload a single consumer if autoscaling lags. A robust retry strategy and queue visibility are essential. The platform that supports REST polling gives you a pull-based alternative when you need to control concurrency tightly.
  • Both providers respect upstream SMTP size limits. Plan for large attachments by streaming to storage rather than embedding base64 in payloads. Link-based attachment delivery with short-lived URLs reduces webhook payload sizes and network overhead.

Deterministic MIME parsing

  • Multi-part alternatives: prefer text over HTML when your application requires plain content, or render HTML and resolve inline CIDs to attachment IDs. Your parser should provide both representations without guessing.
  • Charsets and encodings: normalize to UTF-8, but preserve original byte sequences

Ready to get started?

Start parsing inbound emails with MailParse today.

Get Started Free