Overview
Email attachments present a parsing challenge: they are base64-encoded binary data embedded in a MIME multipart message, sometimes nested multiple levels deep, sometimes encoded as inline images with Content-ID references, and sometimes disguised as body parts by non-conforming mail clients. Extracting them reliably from raw MIME requires careful boundary parsing and encoding handling.
JsonHook handles all of this transparently. Every inbound email is parsed for attachments, and each attachment is described in the webhook payload's email.attachments array. For each attachment you get:
filename— the original filename from the emailcontentType— the MIME type (e.g.,application/pdf,image/png)size— file size in bytescontentId— the Content-ID for inline images (null for regular attachments)
Attachment binary content is not included inline in the webhook payload (to keep payload sizes manageable), but is available for download via the JsonHook Attachments API on Pro plans.
Prerequisites
Requirements for working with email attachments via JsonHook:
- A JsonHook inbound address and webhook endpoint (any plan)
- For downloading attachment content: a Pro plan and your API key
- Storage to save downloaded attachment content (local disk, S3, GCS, etc.)
To test attachment parsing, send an email to your JsonHook address with one or more attached files (PDF, image, CSV, etc.) before building your handler. Inspect the resulting webhook payload to see the attachment array.
Process Email Attachments in Your Pipeline
Attachment metadata included in every payload. Content download available on Pro.
Get Free API KeyStep-by-Step Instructions
Handle email attachments in your webhook pipeline:
- Access the attachments array from the webhook payload:
const attachments = payload.email.attachments; // Each attachment: { filename, contentType, size, contentId, id } - Filter by content type to process only the attachment types your application cares about:
const pdfs = attachments.filter(a => a.contentType === "application/pdf"); const images = attachments.filter(a => a.contentType.startsWith("image/")); - Validate attachment sizes before downloading — reject oversized files to prevent abuse:
const MAX_SIZE = 10 * 1024 * 1024; // 10 MB const valid = attachments.filter(a => a.size <= MAX_SIZE); - Download attachment content (Pro plan) via the Attachments API:
The response is the raw binary file content with the correctGET https://api.jsonhook.com/v1/deliveries/{deliveryId}/attachments/{attachmentId} Authorization: Bearer YOUR_API_KEYContent-Typeheader. - Store the file in your chosen storage backend (S3, filesystem, database BLOB, etc.).
- Process the content as needed — OCR a PDF, resize an image, parse a CSV, etc.
Code Example
This Node.js handler receives an email, finds all PDF attachments, downloads them, and uploads to S3:
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import fetch from "node-fetch";
const s3 = new S3Client({ region: "us-east-1" });
async function processEmailAttachments(payload: any) {
const { email, deliveryId } = payload;
const pdfs = (email.attachments || []).filter(
(a: any) => a.contentType === "application/pdf"
);
for (const attachment of pdfs) {
// Download from JsonHook (Pro plan)
const res = await fetch(
`https://api.jsonhook.com/v1/deliveries/${deliveryId}/attachments/${attachment.id}`,
{ headers: { Authorization: `Bearer ${process.env.JSONHOOK_API_KEY}` } }
);
if (!res.ok) {
console.error(`Failed to download ${attachment.filename}`);
continue;
}
const buffer = Buffer.from(await res.arrayBuffer());
// Upload to S3
await s3.send(new PutObjectCommand({
Bucket: "my-email-attachments",
Key: `${deliveryId}/${attachment.filename}`,
Body: buffer,
ContentType: attachment.contentType,
}));
console.log(`Saved ${attachment.filename} (${attachment.size} bytes) to S3`);
}
}Common Pitfalls
Watch for these attachment handling issues:
- Trusting the filename from the email. Attachment filenames come from the sender and can contain path traversal characters (
../../etc/passwd) or malicious extensions. Always sanitize filenames before using them in filesystem operations. - Not validating the content type. The
contentTypein the attachment metadata comes from the MIME header the sender included — it may not match the actual file content. For security-sensitive pipelines, validate file content server-side (e.g., libmagic, file signatures). - Processing large attachments synchronously. Downloading and processing a large attachment in the webhook handler can cause a timeout. Download attachment content asynchronously in a background job and acknowledge the webhook immediately.
- Ignoring inline images. Inline images (with a
contentId) appear inattachmentswith a non-nullcontentId. If you are archiving all attachments, include inline images. If you only want user-attached files, filter wherecontentId === null. - Not enforcing attachment count or total size limits. An adversarial sender could attach many large files. Set hard limits on the number and total size of attachments you will process per email.