Overview
Raw email is MIME — a text format with headers, body parts, and attachments encoded using a mix of quoted-printable, base64, and raw text, separated by boundary markers. Converting this to a useful JSON structure is non-trivial: you need to handle folded headers, decode character sets, walk nested multipart trees, and normalize inconsistencies introduced by different mail clients.
JsonHook performs this conversion automatically. The output is always the same JSON schema:
{
"email": {
"from": "string",
"to": ["string"],
"cc": ["string"],
"replyTo": "string | null",
"subject": "string",
"date": "ISO8601 string",
"messageId": "string",
"textBody": "string | null",
"htmlBody": "string | null",
"headers": { "header-name": "value" },
"attachments": [
{
"filename": "string",
"contentType": "string",
"size": "number",
"contentId": "string | null"
}
]
},
"deliveryId": "string",
"addressId": "string",
"receivedAt": "ISO8601 string"
}
This schema is consistent regardless of whether the original email was a simple one-line plain text message or a heavily formatted HTML marketing email with ten attachments.
Prerequisites
No special prerequisites beyond a JsonHook account and a webhook endpoint. If you want to understand the full schema in advance:
- Review the payload schema documentation
- Send a few test emails of different types (plain text, HTML, with attachments) to a JsonHook address pointed at webhook.site to see the actual output
Understanding which fields may be null is important before writing handler code. The fields that can be null are: replyTo, textBody, htmlBody, and attachment.contentId.
Get Structured JSON from Every Email
Consistent schema. Zero MIME parsing. Free up to 100 emails/month.
Get Free API KeyStep-by-Step Instructions
To convert your inbound emails to structured JSON:
- Create a JsonHook inbound address via the API or dashboard, pointing at your webhook endpoint.
- Send a variety of test emails to the address — plain text, HTML, with attachments, with CCs, with custom headers.
- Inspect each payload in your delivery log or webhook inspector to understand how different email types map to the schema.
- Write a typed interface or schema for the payload in your language of choice (TypeScript interface, Python dataclass, Go struct) to get compile-time safety when accessing fields.
- Handle nullable fields gracefully throughout your handler code.
- Store or forward the JSON to your database, data warehouse, or downstream services.
Code Example
TypeScript interfaces for the full JsonHook payload, plus a typed handler:
interface JsonHookAttachment {
filename: string;
contentType: string;
size: number;
contentId: string | null;
}
interface JsonHookEmail {
from: string;
to: string[];
cc: string[];
replyTo: string | null;
subject: string;
date: string;
messageId: string;
textBody: string | null;
htmlBody: string | null;
headers: Record<string, string>;
attachments: JsonHookAttachment[];
}
interface JsonHookPayload {
email: JsonHookEmail;
deliveryId: string;
addressId: string;
receivedAt: string;
}
// Handler
app.post("/webhooks/email", express.raw({ type: "application/json" }),
(req, res) => {
// ... verify signature ...
const payload: JsonHookPayload = JSON.parse(req.body.toString());
const { email } = payload;
const body = email.textBody ?? email.htmlBody ?? "";
const senderName = email.from.split("<")[0].trim();
console.log(`${senderName}: ${email.subject} (${email.attachments.length} attachments)`);
res.sendStatus(200);
}
);Common Pitfalls
When working with the structured JSON output:
- Treating
toas a string instead of an array. Thetoandccfields are always arrays, even if there is only one recipient. Accessemail.to[0], notemail.to. - Not handling the case where both textBody and htmlBody are null. Some non-standard emails have no body. Always guard against accessing a null string.
- Assuming the
fromfield is a bare email address. It may be in "Display Name <[email protected]>" format. Parse it appropriately if you need just the address part. - Assuming attachment content is in the payload. Attachment metadata is included; downloading the actual binary content requires a separate API call.
- Not storing the full payload. Even if you only need a few fields today, store the full JSON payload in your database. You will likely want additional fields later and retroactive access is impossible without storage.