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