LettermintLettermint
  • Knowledge base
  • Community
  • Changelog
  • Support
  • Documentation
  • Sending API
  • Team API
Getting started
Guides
Platform
    Projects & Routes
    Emails
    Inbound Mail
      IntroductionCustom domainsProcess inbound emailAttachments and raw emailSpam filtering
    Domains
    Webhooks
    Teams
    Onboarding
Resources
Inbound Mail

Attachments and raw email

Choose how an inbound route sends attachments to your application. Lettermint can include the file in the webhook or provide a temporary signed URL.

ModeChoose it when
Base64 encodedYou want the file in content. This option is suitable for small attachments and makes the webhook larger.
Signed URLsYou process files in a worker. Download each file from url before expires_at.

Each attachment uses one payload shape:

  • A Base64 attachment contains content. It does not contain url or expires_at.
  • A signed URL attachment contains url and expires_at. It does not contain content.

Choose an attachment mode

  1. Open the inbound route and select Settings.
  2. Find Attachments.
  3. Select Base64 encoded or Signed URLs.
  4. Select Save changes.

This setting changes attachments only. Every message.inbound payload includes a signed URL for the original email.

Decode an inline attachment

Code
{ "filename": "invoice-4821.pdf", "content_type": "application/pdf", "size": 18342, "content_id": null, "content": "JVBERi0xLjcKJc..." }

Decode content from Base64 before you scan or store it. Base64 is an encoding method. It is not encryption.

Code
type InlineAttachment = { filename: string content_type: string size: number content_id: string | null content: string } function decodeInlineAttachment(attachment: InlineAttachment): Buffer { const bytes = Buffer.from(attachment.content, 'base64') if (bytes.length !== attachment.size) { throw new Error('Attachment size does not match the webhook metadata') } return bytes }

Download an attachment from a signed URL

Code
{ "filename": "invoice-4821.pdf", "content_type": "application/pdf", "size": 18342, "content_id": null, "url": "https://storage.lettermint.co/inbound/attachments/...?expires=...&signature=...", "expires_at": "2026-09-03T10:15:30+00:00" }

Lettermint keeps the stored attachment for 28 days. The signed URL expires at the end of this period.

Download the file from a background worker. Store it in storage that you control if you need it for more than 28 days.

Code
type UrlAttachment = { filename: string content_type: string size: number content_id: string | null url: string expires_at: string } async function downloadAttachment(attachment: UrlAttachment): Promise<Buffer> { if (Date.parse(attachment.expires_at) <= Date.now()) { throw new Error('Attachment URL has expired') } const response = await fetch(attachment.url, { redirect: 'error' }) if (!response.ok) { throw new Error(`Attachment download failed with ${response.status}`) } const bytes = Buffer.from(await response.arrayBuffer()) if (bytes.length !== attachment.size) { throw new Error('Attachment size does not match the webhook metadata') } return bytes }

Treat each signed URL as a temporary secret. Do not send it to browsers, analytics tools, error trackers, or application logs.

Access the original email

The raw object is always present. It contains a URL for the original RFC 5322 message in .eml format:

Code
{ "raw": { "url": "https://storage.lettermint.co/inbound/raw/...?expires=...&signature=...", "expires_at": "2026-09-03T10:15:30+00:00" } }

Lettermint keeps the raw message for 28 days. The raw message URL expires at the end of this period.

Use the raw message for original headers, MIME boundaries, or content that is not in the parsed fields. Use the parsed webhook fields for normal application logic.

Store files safely

Attachment metadata is untrusted. Apply these controls before you make a file available:

  • Create your own storage key. Do not use filename as a file path.
  • Remove path separators and control characters from the display filename.
  • Detect the file type from the file bytes. Do not trust content_type or the filename extension.
  • Scan the decoded or downloaded file for malware.
  • Apply your size and file type rules before you store the file.
  • If possible, serve stored files as downloads from a separate origin.
  • Do not put signed URLs or attachment content in logs.

An inline image can have a content_id that matches a cid: reference in the HTML body. Sanitize the HTML first. Then map only known content IDs to scanned files.

Lettermint accepts an inbound message with a maximum total size of 25 MB. This limit includes headers, bodies, MIME encoding, and attachments. Base64 content makes the webhook larger than the decoded files. Configure your web server and queue limits for this increase.

See the message.inbound reference for the complete attachment and raw-message fields.

Last modified on August 6, 2026
Process inbound emailSpam filtering
On this page
  • Choose an attachment mode
  • Decode an inline attachment
  • Download an attachment from a signed URL
  • Access the original email
  • Store files safely
JSON
TypeScript
JSON
TypeScript
JSON