Email to JSON: MailParse vs Postmark Inbound

Compare MailParse and Postmark Inbound for Email to JSON. Features, performance, and developer experience.

Why Email to JSON Decides Developer Velocity

Email-to-JSON is a small phrase with a big impact. When inbound email messages arrive, your application needs predictable, clean JSON that handles every quirk of MIME: multipart bodies, weird character encodings, inline images with Content-ID references, threaded replies, and the long tail of headers vendors and clients add. If the conversion is inconsistent, your workflows for ticketing, lead capture, user-generated content, and automation slow down, produce edge case bugs, and require one-off patches.

A well designed email to JSON pipeline should do three things: convert raw email into a normalized JSON envelope that suits application code, deliver that JSON reliably at scale, and make it easy to reprocess or re-fetch when something goes wrong. The details matter: body selection, attachment presentation, header fidelity, signature verification, and idempotency determine how quickly a developer ships and how safely systems operate in production.

This comparison focuses only on converting email to JSON and delivering the structured result to your app. It highlights how two different approaches - webhook only versus webhook plus REST polling - shape the developer experience and operational reliability.

How MailParse Handles Email to JSON

MailParse converts raw MIME into a normalized JSON document purpose built for application consumption. The service provisions instant inbound addresses, accepts messages, parses them into structured fields, and delivers them through a signed webhook or a REST polling API. The goal is to keep your code simple: parse once, consume everywhere.

JSON shape and normalization

  • Envelope and identifiers: id, received_at (ISO 8601 UTC), direction set to inbound, and a stable event_id for idempotency.
  • Addresses: Canonicalized from, to, cc, bcc, and reply_to, each presented as objects with name and email, plus address_string for the original raw value.
  • Headers: A headers object preserves all header fields with unfolded lines and canonical keys, plus headers_raw as an array of {name, value} for exact fidelity.
  • Bodies: text and html are decoded and normalized to UTF-8, with quoted-printable and Base64 decoding, and automatic dominant body selection when one is missing.
  • Attachments: attachments array with filename, content_type, size, inline, content_id, a sha256 checksum, and a time-limited download_url. Large items can be streamed from storage without loading into memory.
  • Inline image resolution: A cid_map makes mapping cid: references in html straightforward.
  • Original MIME: Either raw_mime_url for pull-on-demand, or raw_mime_base64 when you require the complete source inline.
  • Content safety: Optional HTML defanging, and attachment content-type validation with easy allow-listing.

Delivery options and reliability

  • Signed webhooks: POSTs carry an X-Signature HMAC SHA-256 header keyed by your secret. Non-2xx responses trigger exponential backoff retries with jitter, and a dead-letter queue when a threshold is exceeded.
  • REST polling API: Pull messages at your pace using GET /v1/messages?status=ready&limit=100, retrieve bodies with GET /v1/messages/{id}, acknowledge using POST /v1/messages/{id}/ack, and optionally requeue with /retry if downstream work fails.
  • Idempotency: Each event contains a stable event_id, enabling safe de-duplication in your application store.
  • Character encodings: All bodies are normalized to UTF-8 with correct decoding of Shift-JIS, ISO-2022-JP, and other legacy charsets. Long header folding and RFC 2231 filename parameters are respected.

The result is a consistent email-to-JSON experience that lets your ingestion, automation, or support pipelines focus on business logic rather than MIME edge cases.

How Postmark Inbound Handles Email to JSON

Postmark Inbound focuses on a straightforward webhook that posts a parsed JSON payload to your endpoint whenever a message arrives. It is a durable approach that integrates well with queue-backed workers and serverless endpoints where webhooks are the native pattern.

What the inbound JSON typically includes

  • Addresses and envelope: From plus a richer FromFull object, To and ToFull arrays, Cc and CcFull when present, and identifiers such as MessageID and Date.
  • Bodies: TextBody and HtmlBody as strings already decoded for typical consumption.
  • Attachments: An Attachments array with Base64 content, filename, content type, length, and an optional ContentID for inline use.
  • Headers: A collection of original headers, useful for custom routing, threading, and domain-specific integrations.

Delivery model

  • Webhook only: Postmark's inbound posts to your configured URL. Your service returns a 200-level status to acknowledge, otherwise Postmark retries delivery. Your code is responsible for idempotency, storage, and any replay you require.
  • Operational notes: If your endpoint is offline, you rely on Postmark's retry behavior until the message is either delivered or dropped according to their policies. There is no REST polling option for pulling messages on demand.

For many applications already built around webhooks, this design is perfectly serviceable. Teams that prefer pull-based control need to build their own storage layer and worker pollers.

Side-by-Side Comparison of Email to JSON Features

Capability MailParse Postmark Inbound
Delivery methods for parsed JSON Webhook and REST polling Webhook only
Webhook authentication HMAC signature header with secret rotation Webhook to your URL, you implement verification as needed
Idempotency token Stable event_id, documented idempotent retries Recommended via MessageID in your app
Attachments presentation Signed download URLs or direct Base64, plus checksum Base64 in payload with metadata
Inline image mapping Explicit cid_map for easy HTML rewriting Use ContentID fields to resolve cid: references
Character encoding normalization All text normalized to UTF-8 Text and HTML provided as parsed strings
Original MIME access Optional raw_mime_url or inline Base64 Parsed payload focus, store raw MIME yourself if needed
Reprocessing Requeue via API, fetch again anytime Replay by re-sending to your own systems, or manual tools
Error handling Retry with exponential backoff, dead-letter queue, metrics Automatic retries on non-2xx, governed by Postmark policies

Code Examples: Developer Experience for Email to JSON

Webhook verification and consumption with a pull fallback

This example shows receiving a signed webhook, verifying the HMAC, and acknowledging. If your endpoint is down, use the REST polling API to fetch pending messages until the webhook resumes.

// Node.js (Express) - verify HMAC signature and process message
import crypto from 'node:crypto';
import express from 'express';
const app = express();

const SECRET = process.env.WEBHOOK_SECRET;

app.use(express.json({ limit: '20mb' }));

app.post('/webhooks/inbound', (req, res) => {
  const signature = req.get('X-Signature') || '';
  const payload = JSON.stringify(req.body);

  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(payload, 'utf8')
    .digest('hex');

  if (signature !== expected) {
    return res.status(401).send('invalid signature');
  }

  const msg = req.body;
  // Example fields
  // msg.id, msg.received_at, msg.from.email, msg.subject, msg.text, msg.html
  // msg.attachments[i].download_url for streaming

  // Idempotently store by msg.event_id or msg.id
  // enqueue work, then acknowledge
  res.status(204).send();
});

app.listen(3000);
# REST polling loop to drain messages during maintenance
# Fetch ready messages
curl -s -H "Authorization: Bearer $API_TOKEN" \
  "/v1/messages?status=ready&limit=50" | jq '.items[].id' | while read id; do
  id=$(echo $id | tr -d '"')
  # Retrieve full payload
  curl -s -H "Authorization: Bearer $API_TOKEN" "/v1/messages/$id" > "/tmp/$id.json"
  # ...process...
  curl -s -X POST -H "Authorization: Bearer $API_TOKEN" "/v1/messages/$id/ack" > /dev/null
done

Consuming Postmark Inbound webhooks

Postmark Inbound delivers a JSON payload to your endpoint. A 200-level response acknowledges successful processing. Your code should treat the payload as untrusted input, validate sizes, and perform idempotent storage keyed by MessageID.

// Node.js (Express) - Postmark Inbound webhook
import express from 'express';
import { Buffer } from 'node:buffer';
const app = express();

app.use(express.json({ limit: '20mb' }));

app.post('/webhooks/postmark-inbound', async (req, res) => {
  const evt = req.body;

  // Basic fields
  const from = evt.FromFull?.Email || evt.From;
  const subject = evt.Subject || '';
  const text = evt.TextBody || '';
  const html = evt.HtmlBody || '';

  // Attachments
  const attachments = (evt.Attachments || []).map(a => ({
    filename: a.Name,
    contentType: a.ContentType,
    length: a.ContentLength,
    contentId: a.ContentID,
    // Decode on demand to avoid memory pressure
    toBuffer: () => Buffer.from(a.Content || '', 'base64')
  }));

  // Idempotent store keyed by evt.MessageID
  // If processing is successful:
  res.status(200).send('ok');
});

app.listen(3001);
# Example of decoding a single attachment from Postmark Inbound using Python
import base64, json, sys

evt = json.load(sys.stdin)
for att in evt.get('Attachments', []):
    raw = base64.b64decode(att.get('Content', ''))
    with open(att.get('Name', 'attachment.bin'), 'wb') as f:
        f.write(raw)

Performance and Reliability for Email-to-JSON Pipelines

Edge cases that matter in production

  • Malformed MIME boundaries: Real world senders sometimes violate RFCs. A robust parser recovers missing or duplicated boundaries instead of dropping parts.
  • Character sets and encodings: Messages from legacy systems use ISO-2022-JP, Shift-JIS, or Windows-1252. Automatic, correct decoding into UTF-8 prevents mojibake in your database and search index.
  • Large attachments and streaming: Inline Base64 is convenient, but unbounded buffering in your app can cause memory spikes. Prefer download URLs or streamed processing, and set explicit size limits in your handlers.
  • Inline images with cid: references: Clean mapping between Content-ID headers and attachment records avoids broken images in rendered HTML.
  • Reply trimming: When building threaded discussions, choose a consistent strategy for detecting quoted replies and signature delimiters, and fall back to full bodies when confidence is low.

Webhook only versus webhook plus polling

Webhook only systems are elegant when everything is online. When a maintenance window or an upstream incident takes your ingestion offline, you have two choices: temporarily buffer with wider retries or drop payloads into a dead-letter queue and re-inject later. A REST polling option simplifies these scenarios. Your workers can pull a controlled batch size, process at steady-state capacity, and acknowledge only after persistence succeeds. This reduces the blast radius of outages and simplifies backfills after schema changes.

Both patterns can be made reliable, but the operational burden shifts to your team if you need pull semantics. If you prefer preventing webhook exposure from isolated networks, polling from inside your VPC while leaving email acceptance external is also attractive.

For deeper planning around endpoints, retries, and message flow, see Email Infrastructure Checklist for SaaS Platforms and Top Inbound Email Processing Ideas for SaaS Platforms.

Verdict: Which Is Better for Email to JSON?

If your team is comfortable with a webhook only design and you need a concise inbound payload with parsed bodies and attachments, Postmark Inbound delivers that with minimal ceremony. It aligns well with applications that already operate a durable webhook ingestion tier.

If you want both push and pull control, consistent normalization with optional raw MIME access, and built in idempotency plus reprocessing via API, MailParse is the stronger fit. The email-to-JSON conversion is comprehensive, and the REST polling option eliminates a class of operational headaches for teams that prefer controlled batch consumption.

In short, match the delivery model to your architecture. When in doubt, favor the approach that lets you test, replay, and scale without adding custom plumbing. If you are planning a broader foundation for inbound and outbound, the Email Deliverability Checklist for SaaS Platforms is a helpful companion.

FAQ

What does Email to JSON include by default?

At minimum you want normalized sender and recipient objects, subject, decoded plain text and HTML bodies, a complete header set, and an attachments list with filename, content type, size, inline flag, and a reference you can use without storing the entire file in memory. A stable identifier and idempotency token are important for safe retries. Access to the original MIME source is valuable for audits, legal archiving, and manual debugging.

How should I handle attachments safely in my application?

Stream attachments to storage rather than decoding Base64 into memory when files are large. Enforce an allow-list of content types, validate that claimed types match magic bytes where possible, and consider virus scanning before further processing. For inline images referenced by cid:, rewrite HTML to point at your own safe URLs. Avoid persisting untrusted HTML directly into customer visible surfaces without sanitization.

Can I process replies and preserve threading reliably?

Use Message-ID, In-Reply-To, and References headers to associate messages to threads. Heuristically trim quoted replies by matching common delimiters, but always keep the full untrimmed body for auditing. Decide whether to present the trimmed reply or the full text based on confidence in the heuristic and your product's UX.

What if my inbound endpoint is behind a firewall or temporarily offline?

Two common patterns work well. First, allow webhooks only from known IPs to a small ingress tier and place a queue directly behind it. Second, pull new messages from a REST polling API on a schedule and acknowledge only when storage succeeds. MailParse supports both models, which makes migrations and maintenance windows much simpler to manage.

How do I prototype an inbound flow without exposing a public URL?

Use a REST polling workflow or a tunneling tool like ngrok during early development. Polling lets you iterate locally without opening ports. When you are ready to go to production, switch to webhooks for push-latency or keep polling if it matches your control and security requirements. For more ideas on building end to end flows, explore Top Email Parsing API Ideas for SaaS Platforms.

Ready to get started?

Start parsing inbound emails with MailParse today.

Get Started Free