Skip to main content

Sample .eml and MIME Message Fixture API

MOCK DATA

MIME message fixtures with real threading headers, valid multipart boundaries, matching transfer encodings and a raw message that actually parses.

Generated test data for fixtures and development

Base URL
/api/email-messages
Capabilities
3 routes Seedable
Last updated
July 29, 2026

GET /api/email-messages

Each record describes one root message: identification headers, mailboxes, the unfolded header block, the part tree and attachment metadata. Bodies are summarised as bodyPreview — fetch /raw for the complete message text.

Live requestRuns against the public API
No key required
GET/api/email-messages

Request parameters

Part tree: 'plain' = one text/plain entity; 'alternative' = multipart/alternative with text/plain then text/html; 'mixed' = multipart/mixed with a text/plain part plus attachment parts; 'nested' = multipart/mixed wrapping a multipart/alternative plus attachment parts.

Exact number of attachment parts. Requires structure=mixed or structure=nested; anything above 0 with 'plain' or 'alternative' is a 400.

Content-Transfer-Encoding for the text parts; 'quotedPrintable' emits the literal token 'quoted-printable'. Binary attachment parts stay base64 regardless.

Use display names and a subject that contain non-ASCII characters, forcing RFC 2047 'B' encoded-words in From/To/Cc/Subject. Body text and local parts stay US-ASCII either way.

Earliest message date, inclusive. YYYY-MM-DD (= midnight UTC) or a full ISO 8601 timestamp. With only from, to resolves exactly 30 days later; with neither bound, the window is the 30 days up to today at UTC midnight.

Latest message date, inclusive. YYYY-MM-DD means midnight UTC — pass a timestamp to cover that whole day. Must be ≥ from, and the window must contain at least one whole second, because an RFC 5322 Date has second precision. With only to, from resolves exactly 30 days earlier.

Advanced response options7 options

How many records to generate (1–100).

Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed).

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Response format: json envelope, ndjson (one record per line) or csv.

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/email-messages"
const res = await fetch("https://randomapi.dev/api/email-messages");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/email-messages").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/email-messages"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 6
structure enum

Part tree: 'plain' = one text/plain entity; 'alternative' = multipart/alternative with text/plain then text/html; 'mixed' = multipart/mixed with a text/plain part plus attachment parts; 'nested' = multipart/mixed wrapping a multipart/alternative plus attachment parts.

default: alternative
allowed: plain | alternative | mixed | nested
example: structure=nested
attachments int

Exact number of attachment parts. Requires structure=mixed or structure=nested; anything above 0 with 'plain' or 'alternative' is a 400.

default: 0
allowed: 0 – 3
example: attachments=2
encoding enum

Content-Transfer-Encoding for the text parts; 'quotedPrintable' emits the literal token 'quoted-printable'. Binary attachment parts stay base64 regardless.

default: quotedPrintable
allowed: 7bit | quotedPrintable | base64
example: encoding=base64
unicode boolean

Use display names and a subject that contain non-ASCII characters, forcing RFC 2047 'B' encoded-words in From/To/Cc/Subject. Body text and local parts stay US-ASCII either way.

default: false
example: unicode=true
from date

Earliest message date, inclusive. YYYY-MM-DD (= midnight UTC) or a full ISO 8601 timestamp. With only from, to resolves exactly 30 days later; with neither bound, the window is the 30 days up to today at UTC midnight.

example: from=2026-01-01
to date

Latest message date, inclusive. YYYY-MM-DD means midnight UTC — pass a timestamp to cover that whole day. Must be ≥ from, and the window must contain at least one whole second, because an RFC 5322 Date has second precision. With only to, from resolves exactly 30 days earlier.

example: to=2026-03-31
Universal parameters Shared response and formatting options 7
count int

How many records to generate (1–100).

default: 10
allowed: 1 – 100
example: count=3
seed int

Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed).

example: seed=42
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=messageId,inReplyTo
exclude list

Return all fields except these (comma-separated).

example: exclude=bodyPreview
format enum

Response format: json envelope, ndjson (one record per line) or csv.

default: json
allowed: json | ndjson | csv
example: format=csv
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 12
messageId string

Message-ID in <local@domain> form; the domain is always an RFC 2606 reserved name.

example: <20260721T120311.9c1d4a7f0b23@northwind.test>

inReplyTo string nullable

Parent's Message-ID, or null. Always null here — the list route returns root messages; /thread builds the chain.

example: null

references string[]

Ordered ancestor Message-IDs, root first, ending with the direct parent. Always empty here (root messages).

example: []

from object

Sender mailbox: { name, address }.

example: {"name":"Ada Lovelace","address":"ada.lovelace@northwind.test"}

to object[]

One or two primary recipient mailboxes (usually one), always distinct — the same mailbox is never listed twice.

example: [{"name":"Ada Lovelace","address":"ada.lovelace@northwind.test"}]

cc object[]

Zero or one copied mailbox; empty when no Cc header is emitted.

example: []

subject string

Decoded subject. With unicode=true the wire form in headers.Subject is RFC 2047 encoded-words instead.

example: Q3 roadmap review: agenda and open questions

date string (ISO 8601)

The same instant as the Date header, in UTC at second precision — the RFC 5322 form lives in headers.Date.

example: 2026-07-21T12:03:11Z

headers object

Top-level header values in unfolded wire form, keyed by canonical field name (Date, From, To, Cc, Subject, Message-ID, MIME-Version, Content-Type…).

example: {"Date":"Tue, 21 Jul 2026 14:03:11 +0200","From":"\"Ada Lovelace\" <ada.lovelace@northwind.test>","To":"\"Grace Hopper\" <grace.hopper@example.net>","Subject":"Q3 roadmap review: agenda and open questions","Message-ID":"<20260721T120311.9c1d4a7f0b23@northwind.test>","MIME-Version":"1.0","Content-Type":"multipart/alternative; boundary=\"----=_Part_4f2a91c0_8b3d17e6\""}

structure object

Part tree — shape only: contentType, boundary and nested parts for containers; contentType, charset, contentTransferEncoding, decoded sizeBytes and disposition for leaves. Attachment filenames and Content-IDs live in `attachments`.

example: {"contentType":"multipart/alternative","boundary":"----=_Part_4f2a91c0_8b3d17e6","parts":[{"contentType":"text/plain","charset":"utf-8","contentTransferEncoding":"quoted-printable","sizeBytes":412},{"contentType":"text/html","charset":"utf-8","contentTransferEncoding":"quoted-printable","sizeBytes":733}]}

attachments object[]

One entry per attachment part, in part order: filename, contentType, decoded sizeBytes, contentTransferEncoding and contentId.

example: [{"filename":"q3-report.pdf","contentType":"application/pdf","sizeBytes":1843,"contentTransferEncoding":"base64","contentId":"<9f0c2a71b4d8@northwind.test>"}]

bodyPreview string

First 80 characters of the decoded text/plain part with whitespace collapsed, truncated with an ellipsis.

example: Hi Grace, Thanks for the quick turnaround on the last batch - the totals line…

Documented examples

Build-generated requests and complete responses
4
Ten multipart/alternative messages
GET /api/email-messages
{
  "data": [
    {
      "messageId": "<20260702T140933.e6b5fb43cb56@example.net>",
      "inReplyTo": null,
      "references": [],
      "from": {
        "name": "Marcus Nair",
        "address": "marcus.nair@example.net"
      },
      "to": [
        {
          "name": "Marcus Bennett",
          "address": "marcus.bennett@example.com"
        }
      ],
      "cc": [],
      "subject": "Weekly metrics digest",
      "date": "2026-07-02T14:09:33Z",
      "headers": {
        "Date": "Thu, 2 Jul 2026 08:09:33 -0600",
        "From": "\"Marcus Nair\" <marcus.nair@example.net>",
        "To": "\"Marcus Bennett\" <marcus.bennett@example.com>",
        "Subject": "Weekly metrics digest",
        "Message-ID": "<20260702T140933.e6b5fb43cb56@example.net>",
        "MIME-Version": "1.0",
        "Content-Type": "multipart/alternative; boundary=\"----=_Part_8f10d55d_04d4df20\""
      },
      "structure": {
        "contentType": "multipart/alternative",
        "boundary": "----=_Part_8f10d55d_04d4df20",
        "parts": [
          {
            "contentType": "text/plain",
            "charset": "utf-8",
            "contentTransferEncoding": "quoted-printable",
            "sizeBytes": 360
          },
          {
            "contentType": "text/html",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Reproducible mixed messages with attachments
GET /api/email-messages?structure=mixed&attachments=2&seed=42&count=5
{
  "data": [
    {
      "messageId": "<20260702T140933.d4b875a223ab@example.net>",
      "inReplyTo": null,
      "references": [],
      "from": {
        "name": "Marcus Nair",
        "address": "marcus.nair@example.net"
      },
      "to": [
        {
          "name": "Marcus Bennett",
          "address": "marcus.bennett@example.com"
        }
      ],
      "cc": [],
      "subject": "Weekly metrics digest",
      "date": "2026-07-02T14:09:33Z",
      "headers": {
        "Date": "Thu, 2 Jul 2026 08:09:33 -0600",
        "From": "\"Marcus Nair\" <marcus.nair@example.net>",
        "To": "\"Marcus Bennett\" <marcus.bennett@example.com>",
        "Subject": "Weekly metrics digest",
        "Message-ID": "<20260702T140933.d4b875a223ab@example.net>",
        "MIME-Version": "1.0",
        "Content-Type": "multipart/mixed; boundary=\"----=_Part_3b0ba5fe_d363ec65\""
      },
      "structure": {
        "contentType": "multipart/mixed",
        "boundary": "----=_Part_3b0ba5fe_d363ec65",
        "parts": [
          {
            "contentType": "text/plain",
            "charset": "utf-8",
            "contentTransferEncoding": "quoted-printable",
            "sizeBytes": 360
          },
          {
            "contentType": "image/jpeg",
    …
    ]
  }
}
Nested multipart with RFC 2047 headers
GET /api/email-messages?structure=nested&attachments=1&unicode=true&count=3
{
  "data": [
    {
      "messageId": "<20260720T223207.28fa1e70cf67@example.net>",
      "inReplyTo": null,
      "references": [],
      "from": {
        "name": "田中 陽子",
        "address": "yoko.tanaka@example.net"
      },
      "to": [
        {
          "name": "José Álvarez",
          "address": "jose.alvarez@example.com"
        }
      ],
      "cc": [],
      "subject": "Обзор дорожной карты на III квартал",
      "date": "2026-07-20T22:32:07Z",
      "headers": {
        "Date": "Mon, 20 Jul 2026 15:32:07 -0700",
        "From": "=?utf-8?B?55Sw5LitIOmZveWtkA==?= <yoko.tanaka@example.net>",
        "To": "=?utf-8?B?Sm9zw6kgw4FsdmFyZXo=?= <jose.alvarez@example.com>",
        "Subject": "=?utf-8?B?0J7QsdC30L7RgCDQtNC+0YDQvtC20L3QvtC5INC60LDRgNGC?= =?utf-8?B?0Ysg0L3QsCBJSUkg0LrQstCw0YDRgtCw0Ls=?=",
        "Message-ID": "<20260720T223207.28fa1e70cf67@example.net>",
        "MIME-Version": "1.0",
        "Content-Type": "multipart/mixed; boundary=\"----=_Part_011dc810_2416d011\""
      },
      "structure": {
        "contentType": "multipart/mixed",
        "boundary": "----=_Part_011dc810_2416d011",
        "parts": [
          {
            "contentType": "multipart/alternative",
            "boundary": "----=_Part_fd02512b_8e9f0da9",
            "parts": [
              {
                "contentType": "text/plain",
                "charset": "utf-8",
                "contentTransferEncoding": "quoted-printable",
    …
    ]
  }
}
Base64 bodies inside a fixed window
GET /api/email-messages?encoding=base64&from=2026-01-01&to=2026-03-31&count=20
{
  "data": [
    {
      "messageId": "<20260111T153621.e6b5fb43cb56@example.net>",
      "inReplyTo": null,
      "references": [],
      "from": {
        "name": "Marcus Nair",
        "address": "marcus.nair@example.net"
      },
      "to": [
        {
          "name": "Marcus Bennett",
          "address": "marcus.bennett@example.com"
        }
      ],
      "cc": [],
      "subject": "Weekly metrics digest",
      "date": "2026-01-11T15:36:21Z",
      "headers": {
        "Date": "Sun, 11 Jan 2026 09:36:21 -0600",
        "From": "\"Marcus Nair\" <marcus.nair@example.net>",
        "To": "\"Marcus Bennett\" <marcus.bennett@example.com>",
        "Subject": "Weekly metrics digest",
        "Message-ID": "<20260111T153621.e6b5fb43cb56@example.net>",
        "MIME-Version": "1.0",
        "Content-Type": "multipart/alternative; boundary=\"----=_Part_8f10d55d_04d4df20\""
      },
      "structure": {
        "contentType": "multipart/alternative",
        "boundary": "----=_Part_8f10d55d_04d4df20",
        "parts": [
          {
            "contentType": "text/plain",
            "charset": "utf-8",
            "contentTransferEncoding": "base64",
            "sizeBytes": 360
          },
          {
            "contentType": "text/html",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}

GET /api/email-messages/raw

Returns the whole message as a string with CRLF line endings — headers, boundaries, encoded bodies and attachment parts. Save raw verbatim as a .eml file.

Live requestRuns against the public API
No key required
GET/api/email-messages/raw

Request parameters

Part tree: 'plain' = one text/plain entity; 'alternative' = multipart/alternative with text/plain then text/html; 'mixed' = multipart/mixed with a text/plain part plus attachment parts; 'nested' = multipart/mixed wrapping a multipart/alternative plus attachment parts.

Exact number of attachment parts. Requires structure=mixed or structure=nested; anything above 0 with 'plain' or 'alternative' is a 400.

Content-Transfer-Encoding for the text parts; 'quotedPrintable' emits the literal token 'quoted-printable'. Binary attachment parts stay base64 regardless.

Use display names and a subject that contain non-ASCII characters, forcing RFC 2047 'B' encoded-words in From/To/Cc/Subject. Body text and local parts stay US-ASCII either way.

Seeds every choice in the message. Pin it — together with the route's other parameters — for byte-identical output on every call.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/email-messages/raw"
const res = await fetch("https://randomapi.dev/api/email-messages/raw");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/email-messages/raw").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/email-messages/raw"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 5
structure enum

Part tree: 'plain' = one text/plain entity; 'alternative' = multipart/alternative with text/plain then text/html; 'mixed' = multipart/mixed with a text/plain part plus attachment parts; 'nested' = multipart/mixed wrapping a multipart/alternative plus attachment parts.

default: alternative
allowed: plain | alternative | mixed | nested
example: structure=mixed
attachments int

Exact number of attachment parts. Requires structure=mixed or structure=nested; anything above 0 with 'plain' or 'alternative' is a 400.

default: 0
allowed: 0 – 3
example: attachments=2
encoding enum

Content-Transfer-Encoding for the text parts; 'quotedPrintable' emits the literal token 'quoted-printable'. Binary attachment parts stay base64 regardless.

default: quotedPrintable
allowed: 7bit | quotedPrintable | base64
example: encoding=base64
unicode boolean

Use display names and a subject that contain non-ASCII characters, forcing RFC 2047 'B' encoded-words in From/To/Cc/Subject. Body text and local parts stay US-ASCII either way.

default: false
example: unicode=true
seed int

Seeds every choice in the message. Pin it — together with the route's other parameters — for byte-identical output on every call.

default: 1
allowed: 0 – 2147483647
example: seed=42
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=raw,messageId
exclude list

Return all fields except these (comma-separated).

example: exclude=lineCount
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 9
raw string

The complete message: CRLF line endings, pure US-ASCII, no trailing newline. Never larger than about 16 KB (three base64 attachments of at most 2400 octets each).

example: Date: Tue, 21 Jul 2026 14:03:11 +0200 From: "Ada Lovelace" <ada.lovelace@northwind.test> …

messageId string

The Message-ID of this message, including angle brackets.

example: <20260721T120311.9c1d4a7f0b23@northwind.test>

subject string

Decoded subject — the wire form inside `raw` is RFC 2047 encoded-words when unicode=true.

example: Q3 roadmap review: agenda and open questions

date string (ISO 8601)

The instant in the Date header, in UTC. Derived from `seed` inside a fixed 2020-01-01…2029-12-31 window, so the message never changes over time.

example: 2026-07-21T12:03:11Z

structure object

The part tree of this exact message, with the boundary strings that appear in `raw`.

example: {"contentType":"multipart/alternative","boundary":"----=_Part_4f2a91c0_8b3d17e6","parts":[{"contentType":"text/plain","charset":"utf-8","contentTransferEncoding":"quoted-printable","sizeBytes":412},{"contentType":"text/html","charset":"utf-8","contentTransferEncoding":"quoted-printable","sizeBytes":733}]}

attachments object[]

One entry per attachment part, in part order: filename, contentType, decoded sizeBytes, contentTransferEncoding and contentId.

example: [{"filename":"q3-report.pdf","contentType":"application/pdf","sizeBytes":1843,"contentTransferEncoding":"base64","contentId":"<9f0c2a71b4d8@northwind.test>"}]

boundaries string[]

Every boundary string used, outermost first. Empty for structure=plain. None of them occurs inside any part body.

example: ["----=_Part_4f2a91c0_8b3d17e6"]

sizeBytes integer

Byte length of `raw`. Equal to its character count, because the message is pure US-ASCII.

example: 1893

lineCount integer

Number of CRLF-separated lines in `raw`. Every line is at most 998 characters, per RFC 5322 §2.1.1.

example: 42

Documented examples

Build-generated requests and complete responses
4
A complete multipart/alternative message
GET /api/email-messages/raw
{
  "data": {
    "raw": "Date: Tue, 4 Jan 2022 15:26:55 -0500\r\nFrom: \"Ruth Lovelace\" <ruth.lovelace@kestrel.test>\r\nTo: \"Marcus Duarte\" <marcus.duarte@example.com>\r\nCc: \"Maya Ferreira\" <maya.ferreira@northwind.test>\r\nSubject: Storage quota is at 82 percent\r\nMessage-ID: <20220104T202655.3af16ffebb17@kestrel.test>\r\nMIME-Version: 1.0\r\nContent-Type: multipart/alternative; boundary=\"----=_Part_68265634_fd45b22f\"\r\n\r\nThis is a multipart message in MIME format.\r\n------=_Part_68265634_fd45b22f\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nHi Marcus,\r\n\r\nCould you confirm the owner for the second workstream before Friday? I woul=\r\nd like to close the tracker.\r\n\r\nWe are holding the release until the migration finishes, which should be so=\r\nme time on Wednesday afternoon.\r\n\r\nMore detail: https://kestrel.test/reports/east-c6c5?utm_source=3Ddigest&utm=\r\n_medium=3Demail\r\n\r\n--=20\r\nRuth Lovelace\r\nData, Kestrel Analytics\r\n------=_Part_68265634_fd45b22f\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<!DOCTYPE html>\r\n<html lang=3D\"en\">\r\n<body>\r\n<p>Hi Marcus,</p>\r\n<p>Could you confirm the owner for the second workstream before Friday? I=\r\n would like to close the tracker.</p>\r\n<p>We are holding the release until the migration finishes, which should be=\r\n some time on Wednesday afternoon.</p>\r\n<p><a href=3D\"https://kestrel.test/reports/east-c6c5?utm_source=3Ddigest&am=\r\np;utm_medium=3Demail\">Open the report</a></p>\r\n<p style=3D\"color:#6b7280\">Ruth Lovelace<br>Data, Kestrel Analytics</p>\r\n</body>\r\n</html>\r\n------=_Part_68265634_fd45b22f--",
    "messageId": "<20220104T202655.3af16ffebb17@kestrel.test>",
    "subject": "Storage quota is at 82 percent",
    "date": "2022-01-04T20:26:55Z",
    "structure": {
      "contentType": "multipart/alternative",
      "boundary": "----=_Part_68265634_fd45b22f",
      "parts": [
        {
          "contentType": "text/plain",
          "charset": "utf-8",
          "contentTransferEncoding": "quoted-printable",
          "sizeBytes": 364
        },
        {
          "contentType": "text/html",
          "charset": "utf-8",
          "contentTransferEncoding": "quoted-printable",
          "sizeBytes": 492
        }
      ]
    },
    "attachments": [],
    "boundaries": [
      "----=_Part_68265634_fd45b22f"
    ],
    "sizeBytes": 1598,
    "lineCount": 46
  },
  "meta": {
    "endpoint": "email-messages",
    "route": "/raw",
    "params": {
      "structure": "alternative",
      "attachments": 0,
      "encoding": "quotedPrintable",
      "unicode": false,
      "seed": 1
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Nested multipart with two attachments
GET /api/email-messages/raw?structure=nested&attachments=2&seed=7
{
  "data": {
    "raw": "Date: Mon, 12 Oct 2026 05:30:00 -0600\r\nFrom: \"Isaac Hopper\" <isaac.hopper@example.com>\r\nTo: \"Alan Bennett\" <alan.bennett@meridian.test>\r\nCc: \"Isaac Moreau\" <isaac.moreau@northwind.test>\r\nSubject: Deploy window moved to Thursday 09:00\r\nMessage-ID: <20261012T113000.be33ae467d4a@example.com>\r\nMIME-Version: 1.0\r\nContent-Type: multipart/mixed; boundary=\"----=_Part_64961e4a_814a8074\"\r\n\r\nThis is a multipart message in MIME format.\r\n------=_Part_64961e4a_814a8074\r\nContent-Type: multipart/alternative; boundary=\"----=_Part_47a6f811_20ed7944\"\r\n\r\n------=_Part_47a6f811_20ed7944\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nHi Alan,\r\n\r\nNothing urgent, but the archive job has been running long enough that it is=\r\n worth a look this week.\r\n\r\nWe are holding the release until the migration finishes, which should be so=\r\nme time on Wednesday afternoon.\r\n\r\nMore detail: https://example.com/reports/central-d963?utm_source=3Ddigest&u=\r\ntm_medium=3Demail\r\n\r\n--=20\r\nIsaac Hopper\r\nSupport, Example Corp\r\n------=_Part_47a6f811_20ed7944\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<!DOCTYPE html>\r\n<html lang=3D\"en\">\r\n<body>\r\n<p>Hi Alan,</p>\r\n<p>Nothing urgent, but the archive job has been running long enough that it=\r\n is worth a look this week.</p>\r\n<p>We are holding the release until the migration finishes, which should be=\r\n some time on Wednesday afternoon.</p>\r\n<p><a href=3D\"https://example.com/reports/central-d963?utm_source=3Ddigest&=\r\namp;utm_medium=3Demail\">Open the report</a></p>\r\n<p style=3D\"color:#6b7280\">Isaac Hopper<br>Support, Example Corp</p>\r\n</body>\r\n</html>\r\n------=_Part_47a6f811_20ed7944--\r\n------=_Part_64961e4a_814a8074\r\nContent-Type: image/jpeg\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"scan.jpg\"\r\nContent-ID: <12615a93725d@example.com>\r\n\r\nKq7wT866zyWkKZ8Lf2ddh8fuwx5Zm/CZk+5bxH5zEEqf4c08oGTkMSd2PG0zR2EUdtcDHAicwpwO\r\niZgY7GpFSYY2l0EFJZvVMmTeXj4euheW/MMCpMvTCM3Xp5O/KQmvtntAVxPtrTBMrGEgLGY+ZBut\r\nD8ohYCi1xJbKN0a2IXakJi/res7ycbdMrm2GNNOZ+SbVEUYr2uIgffdyvYEnwidMYaYHQGePMbA5\r\nbnKq5ohiKjDKaDcUTLW5Bp+HSXzyoKixbo9k2/nhNlb2QcVlltUtU0fUpebhZ3OjJ+rNbHVxmFSd\r\nX7gAcnluz9/NXoGfk0EVnlNbsoWiJ/WsPKqn+UAoE7NuC5pmHMkFOtjacs8kAab5ekdO80L6cHhq\r\nZmpRbuBlOpOBCduGn+xV+PSzqIOZ79jfCdI2N7aVz92oC7JCx4J8KG4dJBc4r97iZVkXHd12UOjP\r\nacFCtpVLQGj6vRZVFpWp/9pM1cLUGYtLrFbLByvCmmK1B19p9YYLQdh71LO6rbC84C+ZJjVBgWrg\r\nFBBrS+bSiZpNGhMOHDUv7SxPlUjxcbvJc/OFIsnKRMRQEBl1tkBmtsIGtsNCMv49tovPhntzgnzk\r\nc1R0spP5lUEGpI+9LUx1eAyQ9Y4WSwy/tzvDf6IbzcoLyl5B/vbavz+wVpjtzcGXTh+fwPW0Xe5s\r\nECDbURISp6MbXwJIEQCvJuinO/E2c/41ajU2k4Z7KV0UA791V1IVs2EI8IvUqJ8RtP1hzLmr0m40\r\nxAoFihEXv4fTPoHJnqLoKXxMF090HpVnWZziJACCjh1D40Mx4vRh76PL13KYueQcN5pniij9wOb4\r\n7tJmVduL5gr1NWSCZD2Oxd554VGI0tfrb9Vh+Dk1B4xvDkCtgKgfWOU87tKOfXqrfmcisurgzogo\r\nYbwhV95QPtFMcc2piM5xgrd+AZSa7/kACx3rql4TlMYTH1JkjUTVA2JB7YNcrgSTTlFk6hRdSNuF\r\nPXedWDXaUCvpSmuKeq3CNXO6Y+u2cN855qOkNYL8UtT6zs8w80I6K71IW367Q53JXsoghLQGeB0I\r\nmhCIUzEplxt1tcGJQlTrlwBgEviYF8VF425sHxIYOGcQ7BPre8EyMwDZVuD9ew97s+m8Rn3zG3vE\r\nZnBpPaPg93tbEkhR/1PHqXQ6NOcgvjcFPsMgMn9Kl99umkv+FEjMYWOacNuse/nK9PaDfacTqOaZ\r\nfiXVuwOq9nhKgp85qbA6PTDnGpPH6EUnaK8jTmTNPseuVPlx3N2S3YT9WD13PurVivUdOqGdHB1a\r\nnCoKcYNg2SkR0B7wx984cCda2XGAw3giFqw0iaRJM1oCOfS/3h9WDeUCgJyy7OUVhHEpy0OL9/GE\r\n4mBINW8sAtSk1yV9oUfrGzsF6VPzTy17e59OSwza3XKzVw7p6Odwg9TBfU/mJEMNNlVymKmnI/jA\r\nuV0yMacXfsVBEwCzzmcMOe5zPP5AD8FuYkfrDSFrNvWm5XIpan5UAWPvLAj28uSuVg==\r\n------=_Part_64961e4a_814a8074\r\nContent-Type: application/pdf\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"q3-report.pdf\"\r\nContent-ID: <bba837db838b@example.com>\r\n\r\n36ATbNoaduNVv1GSDmJCBGW0Nf3Kf0CKUpHeu+MGe0zuGwY8JOXdXr8Z9O+Bfm6EqmM58FzTnLYa\r\nFuMD1ofdjZ5b8vkzSTJSNsUzBR9jokaIAIoeupZRFyuziBtHvl9SZXjOqUhh+ccGZlz8UboAzHfM\r\nga7JHBJ1Y5/zJsyFl8qS9ud6+E/vhv1V9z6dw40+IcUVsjC6IeFYwPaAsxivQGpLSqKhhHcrkKRI\r\ndrhrB1sPbA+xh6pU1ytIHpA35UorWI4WvZmKvgc+3uCvUWe/iPIk77ehohSWsOVDKcADXvNiCw+V\r\ni5VzU2/VydP1sOA3vvaG0gJEKIe1Zs1nQeey5tt8+/HoFgfR3dGDF5wFYVg/RJ9BFjzppYbV+7aw\r\npuyMAKRRnUknAOWBU3QnoYmAT5oekeg7AgdofmWTN4y2NUZSFmX4JnUJPSX6AeFu8bH/36PmLs2L\r\n0t7FFvak08mRJPIIwyUKOofiM3e7ECfvcPHPU++v/jWwSYblmmBBDWHOdFECqHhDK/5ObEtZV5cm\r\nHhBCXKEq2wFiEDg5NIqeoYn7dUkTfUf3PqW0XPvCYh5lBsKnkDRJCtJLI8+DNe0Cq5vU8RMyW1m5\r\nyZ5OSyVehfkyR8fRTfrXmCqFhiJsQEOhVBL92wAJHMd9V3mqEa+tAqC4pQqQtCAQDuV+imiWVNZ/\r\nsO0TpwebR22yHL8hs2KbeuHBz8hiKig/I4IuoVidreyfBmT711T+/5ert9ZnSy4mJZPRzaMwvjc/\r\nK54RVs2/IbjVEwEMbVZgDlo1EZP2Qn8DxQNYkWc/V0s55ei/AQkZzMxMWMMw9cdELn1NRRlgBJ9U\r\nDo0+h34BrNvsCCCWr+tAkwc6TWjymfzFp7qcOtK8hAOZC43gxYPrZLNJbkYQFZnUVm6F1ejzazEB\r\no6RpUyPWyN3IAumKeW5bcXmF9CyTA/P7pNUD5kmTOZAaJDsRvLzzbYmDy5z4s0ewpePdu1ixOQKP\r\nd41+\r\n------=_Part_64961e4a_814a8074--",
    "messageId": "<20261012T113000.be33ae467d4a@example.com>",
    "subject": "Deploy window moved to Thursday 09:00",
    "date": "2026-10-12T11:30:00Z",
    "structure": {
      "contentType": "multipart/mixed",
      "boundary": "----=_Part_64961e4a_814a8074",
      "parts": [
        {
          "contentType": "multipart/alternative",
          "boundary": "----=_Part_47a6f811_20ed7944",
          "parts": [
            {
              "contentType": "text/plain",
              "charset": "utf-8",
              "contentTransferEncoding": "quoted-printable",
              "sizeBytes": 357
            },
            {
              "contentType": "text/html",
              "charset": "utf-8",
              "contentTransferEncoding": "quoted-printable",
              "sizeBytes": 485
            }
          ]
        },
        {
          "contentType": "image/jpeg",
          "contentTransferEncoding": "base64",
          "sizeBytes": 1132,
          "disposition": "attachment"
        },
        {
          "contentType": "application/pdf",
          "contentTransferEncoding": "base64",
          "sizeBytes": 744,
          "disposition": "attachment"
        }
    …
    ]
  }
}
Single-part 7bit message
GET /api/email-messages/raw?structure=plain&encoding=7bit
{
  "data": {
    "raw": "Date: Tue, 4 Jan 2022 15:26:55 -0500\r\nFrom: \"Ruth Lovelace\" <ruth.lovelace@kestrel.test>\r\nTo: \"Marcus Duarte\" <marcus.duarte@example.com>\r\nCc: \"Maya Ferreira\" <maya.ferreira@northwind.test>\r\nSubject: Storage quota is at 82 percent\r\nMessage-ID: <20220104T202655.3af16ffebb17@kestrel.test>\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHi Marcus,\r\n\r\nCould you confirm the owner for the second workstream before Friday? I would like to close the tracker.\r\n\r\nWe are holding the release until the migration finishes, which should be some time on Wednesday afternoon.\r\n\r\nMore detail: https://kestrel.test/reports/east-c6c5?utm_source=digest&utm_medium=email\r\n\r\n-- \r\nRuth Lovelace\r\nData, Kestrel Analytics",
    "messageId": "<20220104T202655.3af16ffebb17@kestrel.test>",
    "subject": "Storage quota is at 82 percent",
    "date": "2022-01-04T20:26:55Z",
    "structure": {
      "contentType": "text/plain",
      "charset": "utf-8",
      "contentTransferEncoding": "7bit",
      "sizeBytes": 364
    },
    "attachments": [],
    "boundaries": [],
    "sizeBytes": 748,
    "lineCount": 21
  },
  "meta": {
    "endpoint": "email-messages",
    "route": "/raw",
    "params": {
      "structure": "plain",
      "attachments": 0,
      "encoding": "7bit",
      "unicode": false,
      "seed": 1
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Base64 bodies and encoded-word headers
GET /api/email-messages/raw?encoding=base64&unicode=true&seed=99
{
  "data": {
    "raw": "Date: Thu, 4 Jan 2029 20:41:26 +0900\r\nFrom: =?utf-8?B?w5NsYWZ1ciBKw7Nuc3Nvbg==?= <olafur.jonsson@example.com>\r\nTo: =?utf-8?B?Wm/DqyBNw7xsbGVy?= <zoe.mueller@orbital.test>\r\nCc: =?utf-8?B?Wm/DqyBNw7xsbGVy?= <zoe.mueller@bluewave.test>\r\nSubject: =?utf-8?B?QcOxYWRpciBzb3BvcnRlIHBhcmEgYsO6c3F1ZWRhIGF2YW56?=\r\n =?utf-8?B?YWRh?=\r\nMessage-ID: <20290104T114126.b420eca9967b@example.com>\r\nMIME-Version: 1.0\r\nContent-Type: multipart/alternative; boundary=\"----=_Part_0ba2ce56_97206372\"\r\n\r\nThis is a multipart message in MIME format.\r\n------=_Part_0ba2ce56_97206372\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n\r\nSGkgWm9lLA0KDQpJIGhhdmUgcGVuY2lsbGVkIGluIGEgc2hvcnQgY2FsbCBmb3IgVHVlc2RheTsg\r\nbW92ZSBpdCBpZiB0aGF0IGNsYXNoZXMgd2l0aCB0aGUgY3VzdG9tZXIgd29ya3Nob3AuDQoNClRo\r\nYW5rcyBmb3IgdGhlIHF1aWNrIHR1cm5hcm91bmQgb24gdGhlIGxhc3QgYmF0Y2ggLSB0aGUgdG90\r\nYWxzIGxpbmUgdXAgd2l0aCB3aGF0IHdlIHNhdyBpbiBzdGFnaW5nLg0KDQpNb3JlIGRldGFpbDog\r\naHR0cHM6Ly9leGFtcGxlLmNvbS9yZXBvcnRzL2liZXJpYS04MjY5P3V0bV9zb3VyY2U9ZGlnZXN0\r\nJnV0bV9tZWRpdW09ZW1haWwNCg0KLS0gDQpPbGFmdXIgSm9uc3Nvbg0KU3VwcG9ydCwgRXhhbXBs\r\nZSBDb3Jw\r\n------=_Part_0ba2ce56_97206372\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n\r\nPCFET0NUWVBFIGh0bWw+DQo8aHRtbCBsYW5nPSJlbiI+DQo8Ym9keT4NCjxwPkhpIFpvZSw8L3A+\r\nDQo8cD5JIGhhdmUgcGVuY2lsbGVkIGluIGEgc2hvcnQgY2FsbCBmb3IgVHVlc2RheTsgbW92ZSBp\r\ndCBpZiB0aGF0IGNsYXNoZXMgd2l0aCB0aGUgY3VzdG9tZXIgd29ya3Nob3AuPC9wPg0KPHA+VGhh\r\nbmtzIGZvciB0aGUgcXVpY2sgdHVybmFyb3VuZCBvbiB0aGUgbGFzdCBiYXRjaCAtIHRoZSB0b3Rh\r\nbHMgbGluZSB1cCB3aXRoIHdoYXQgd2Ugc2F3IGluIHN0YWdpbmcuPC9wPg0KPHA+PGEgaHJlZj0i\r\naHR0cHM6Ly9leGFtcGxlLmNvbS9yZXBvcnRzL2liZXJpYS04MjY5P3V0bV9zb3VyY2U9ZGlnZXN0\r\nJmFtcDt1dG1fbWVkaXVtPWVtYWlsIj5PcGVuIHRoZSByZXBvcnQ8L2E+PC9wPg0KPHAgc3R5bGU9\r\nImNvbG9yOiM2YjcyODAiPk9sYWZ1ciBKb25zc29uPGJyPlN1cHBvcnQsIEV4YW1wbGUgQ29ycDwv\r\ncD4NCjwvYm9keT4NCjwvaHRtbD4=\r\n------=_Part_0ba2ce56_97206372--",
    "messageId": "<20290104T114126.b420eca9967b@example.com>",
    "subject": "Añadir soporte para búsqueda avanzada",
    "date": "2029-01-04T11:41:26Z",
    "structure": {
      "contentType": "multipart/alternative",
      "boundary": "----=_Part_0ba2ce56_97206372",
      "parts": [
        {
          "contentType": "text/plain",
          "charset": "utf-8",
          "contentTransferEncoding": "base64",
          "sizeBytes": 348
        },
        {
          "contentType": "text/html",
          "charset": "utf-8",
          "contentTransferEncoding": "base64",
          "sizeBytes": 476
        }
      ]
    },
    "attachments": [],
    "boundaries": [
      "----=_Part_0ba2ce56_97206372"
    ],
    "sizeBytes": 1907,
    "lineCount": 36
  },
  "meta": {
    "endpoint": "email-messages",
    "route": "/raw",
    "params": {
      "structure": "alternative",
      "attachments": 0,
      "encoding": "base64",
      "unicode": true,
      "seed": 99
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}

GET /api/email-messages/thread

Returns the root message followed by depth replies. Each reply's In-Reply-To is exactly its parent's Message-ID, and its References is the parent's References plus the parent's Message-ID (RFC 5322 §3.6.4).

Live requestRuns against the public API
No key required
GET/api/email-messages/thread

Request parameters

Number of replies after the root message; the chain therefore holds depth + 1 messages.

Part tree: 'plain' = one text/plain entity; 'alternative' = multipart/alternative with text/plain then text/html; 'mixed' = multipart/mixed with a text/plain part plus attachment parts; 'nested' = multipart/mixed wrapping a multipart/alternative plus attachment parts.

Earliest possible date for the root message, which lands within 12 hours of it; replies then follow at 7 minutes to 20 hours apart. Every instant in the chain is a whole second (a sub-second 'from' rounds up), because an RFC 5322 Date has second precision. Defaults to 7 days before today (UTC).

Seeds every choice in the message. Pin it — together with the route's other parameters — for byte-identical output on every call.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/email-messages/thread"
const res = await fetch("https://randomapi.dev/api/email-messages/thread");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/email-messages/thread").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/email-messages/thread"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 4
depth int

Number of replies after the root message; the chain therefore holds depth + 1 messages.

default: 3
allowed: 1 – 8
example: depth=5
structure enum

Part tree: 'plain' = one text/plain entity; 'alternative' = multipart/alternative with text/plain then text/html; 'mixed' = multipart/mixed with a text/plain part plus attachment parts; 'nested' = multipart/mixed wrapping a multipart/alternative plus attachment parts.

default: alternative
allowed: plain | alternative | mixed | nested
example: structure=plain
from date

Earliest possible date for the root message, which lands within 12 hours of it; replies then follow at 7 minutes to 20 hours apart. Every instant in the chain is a whole second (a sub-second 'from' rounds up), because an RFC 5322 Date has second precision. Defaults to 7 days before today (UTC).

example: from=2026-05-04
seed int

Seeds every choice in the message. Pin it — together with the route's other parameters — for byte-identical output on every call.

default: 1
allowed: 0 – 2147483647
example: seed=42
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=subject,depth
exclude list

Return all fields except these (comma-separated).

example: exclude=messages
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 5
subject string

Base subject. The root uses it verbatim; every reply uses 'Re: ' plus the base (never stacked).

example: Q3 roadmap review: agenda and open questions

depth integer (1-8)

Replies after the root, as requested.

example: 3

messageCount integer

Messages in the chain — always depth + 1.

example: 4

participants object[]

The two or three mailboxes in the conversation, in send-rotation order: message i is sent by participants[i % participants.length]. Everyone else on a message is a To or Cc recipient, so at depth=1 a third participant is only ever copied, never a sender.

example: [{"name":"Ada Lovelace","address":"ada.lovelace@northwind.test"},{"name":"Grace Hopper","address":"grace.hopper@example.net"},{"name":"Marcus Bennett","address":"marcus.bennett@meridian.test"}]

messages object[]

The chain, oldest first. Each entry has index, messageId, inReplyTo, references, from, to, cc, subject, date, headers, structure and bodyPreview. Thread messages never carry attachment parts.

example: [{"index":0,"messageId":"<20260721T120311.9c1d4a7f0b23@northwind.test>","inReplyTo":null,"references":[],"from":{"name":"Ada Lovelace","address":"ada.lovelace@northwind.test"},"to":[{"name":"Grace Hopper","address":"grace.hopper@example.net"}],"cc":[{"name":"Marcus Bennett","address":"marcus.bennett@meridian.test"}],"subject":"Q3 roadmap review: agenda and open questions","date":"2026-07-21T12:03:11Z","headers":{"Date":"Tue, 21 Jul 2026 14:03:11 +0200","From":"\"Ada Lovelace\" <ada.lovelace@northwind.test>","To":"\"Grace Hopper\" <grace.hopper@example.net>","Cc":"\"Marcus Bennett\" <marcus.bennett@meridian.test>","Subject":"Q3 roadmap review: agenda and open questions","Message-ID":"<20260721T120311.9c1d4a7f0b23@northwind.test>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"quoted-printable"},"structure":{"contentType":"text/plain","charset":"utf-8","contentTransferEncoding":"quoted-printable","sizeBytes":412},"bodyPreview":"Hi Grace, Thanks for the quick turnaround on the last batch - the totals line…"},{"index":1,"messageId":"<20260721T224408.3e81b6cf90a4@example.net>","inReplyTo":"<20260721T120311.9c1d4a7f0b23@northwind.test>","references":["<20260721T120311.9c1d4a7f0b23@northwind.test>"],"from":{"name":"Grace Hopper","address":"grace.hopper@example.net"},"to":[{"name":"Ada Lovelace","address":"ada.lovelace@northwind.test"}],"cc":[{"name":"Marcus Bennett","address":"marcus.bennett@meridian.test"}],"subject":"Re: Q3 roadmap review: agenda and open questions","date":"2026-07-21T22:44:08Z","headers":{"Date":"Wed, 22 Jul 2026 07:44:08 +0900","From":"\"Grace Hopper\" <grace.hopper@example.net>","To":"\"Ada Lovelace\" <ada.lovelace@northwind.test>","Cc":"\"Marcus Bennett\" <marcus.bennett@meridian.test>","Subject":"Re: Q3 roadmap review: agenda and open questions","Message-ID":"<20260721T224408.3e81b6cf90a4@example.net>","In-Reply-To":"<20260721T120311.9c1d4a7f0b23@northwind.test>","References":"<20260721T120311.9c1d4a7f0b23@northwind.test>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"quoted-printable"},"structure":{"contentType":"text/plain","charset":"utf-8","contentTransferEncoding":"quoted-printable","sizeBytes":338},"bodyPreview":"Hi Ada, Looks right. The only thing I would add is a note about the retry budge…"}]

Documented examples

Build-generated requests and complete responses
3
Three replies from the past week
GET /api/email-messages/thread
{
  "data": {
    "subject": "Storage quota is at 82 percent",
    "depth": 3,
    "messageCount": 4,
    "participants": [
      {
        "name": "Ruth Lovelace",
        "address": "ruth.lovelace@kestrel.test"
      },
      {
        "name": "Priya Duarte",
        "address": "priya.duarte@example.com"
      }
    ],
    "messages": [
      {
        "index": 0,
        "messageId": "<20260722T085421.fdc7372805dc@kestrel.test>",
        "inReplyTo": null,
        "references": [],
        "from": {
          "name": "Ruth Lovelace",
          "address": "ruth.lovelace@kestrel.test"
        },
        "to": [
          {
            "name": "Priya Duarte",
            "address": "priya.duarte@example.com"
          }
        ],
        "cc": [],
        "subject": "Storage quota is at 82 percent",
        "date": "2026-07-22T08:54:21Z",
        "headers": {
          "Date": "Wed, 22 Jul 2026 00:54:21 -0800",
          "From": "\"Ruth Lovelace\" <ruth.lovelace@kestrel.test>",
          "To": "\"Priya Duarte\" <priya.duarte@example.com>",
          "Subject": "Storage quota is at 82 percent",
          "Message-ID": "<20260722T085421.fdc7372805dc@kestrel.test>",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Deepest chain from a fixed date
GET /api/email-messages/thread?depth=8&from=2026-05-04&seed=11
{
  "data": {
    "subject": "Contract renewal - next steps",
    "depth": 8,
    "messageCount": 9,
    "participants": [
      {
        "name": "Tobias Nair",
        "address": "tobias.nair@acme.test"
      },
      {
        "name": "Julian Halvorsen",
        "address": "julian.halvorsen@northwind.test"
      }
    ],
    "messages": [
      {
        "index": 0,
        "messageId": "<20260504T031539.8059e3f02c61@acme.test>",
        "inReplyTo": null,
        "references": [],
        "from": {
          "name": "Tobias Nair",
          "address": "tobias.nair@acme.test"
        },
        "to": [
          {
            "name": "Julian Halvorsen",
            "address": "julian.halvorsen@northwind.test"
          }
        ],
        "cc": [],
        "subject": "Contract renewal - next steps",
        "date": "2026-05-04T03:15:39Z",
        "headers": {
          "Date": "Mon, 4 May 2026 12:15:39 +0900",
          "From": "\"Tobias Nair\" <tobias.nair@acme.test>",
          "To": "\"Julian Halvorsen\" <julian.halvorsen@northwind.test>",
          "Subject": "Contract renewal - next steps",
          "Message-ID": "<20260504T031539.8059e3f02c61@acme.test>",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Plain-text chain
GET /api/email-messages/thread?depth=2&structure=plain
{
  "data": {
    "subject": "Storage quota is at 82 percent",
    "depth": 2,
    "messageCount": 3,
    "participants": [
      {
        "name": "Ruth Lovelace",
        "address": "ruth.lovelace@kestrel.test"
      },
      {
        "name": "Priya Duarte",
        "address": "priya.duarte@example.com"
      }
    ],
    "messages": [
      {
        "index": 0,
        "messageId": "<20260722T085421.fdc7372805dc@kestrel.test>",
        "inReplyTo": null,
        "references": [],
        "from": {
          "name": "Ruth Lovelace",
          "address": "ruth.lovelace@kestrel.test"
        },
        "to": [
          {
            "name": "Priya Duarte",
            "address": "priya.duarte@example.com"
          }
        ],
        "cc": [],
        "subject": "Storage quota is at 82 percent",
        "date": "2026-07-22T08:54:21Z",
        "headers": {
          "Date": "Wed, 22 Jul 2026 00:54:21 -0800",
          "From": "\"Ruth Lovelace\" <ruth.lovelace@kestrel.test>",
          "To": "\"Priya Duarte\" <priya.duarte@example.com>",
          "Subject": "Storage quota is at 82 percent",
          "Message-ID": "<20260722T085421.fdc7372805dc@kestrel.test>",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}

About this API

Coverage & behavior

Generates RFC 5322 / MIME messages you can point a parser at: structured records on the root route, one complete raw message on /raw, and a linked reply chain on /thread. The invariants below are the reason this exists — they are exactly the things a hand-written fixture gets subtly wrong.

  • Threading is correct by construction. In a /thread chain, In-Reply-To is exactly the parent's Message-ID, and References is the parent's References followed by the parent's Message-ID — root first, ending with the direct parent (RFC 5322 §3.6.4). That is the rule broken thread views get wrong, and ?depth=8 gives you eight generations to test a grouping algorithm against.
  • Boundaries are proven, not merely random. Every part body is encoded first; the boundary is then chosen so that it occurs nowhere in any child block (RFC 2046 §5.1.1), lengthening the candidate until that is true.
  • The transfer encoding matches the bytes. base64 bodies wrap at 76 characters, quotedPrintable emits =3D for literal equals signs, soft-breaks with a trailing = at 76 characters and escapes the trailing space of the -- signature separator, and 7bit is only ever used for bodies that really are US-ASCII.
  • Ordering, dates and folding. In multipart/alternative, text/plain precedes text/html (least to most faithful, RFC 2046 §5.1.4). Date is an RFC 5322 date-time such as Tue, 21 Jul 2026 14:03:11 +0200not ISO 8601; the ISO form of the same instant is the date field. Long headers fold at 76 characters with CRLF plus a single space, and unfold back to the original byte for byte.

Structures. plain is a single text/plain entity. alternative is multipart/alternative [text/plain, text/html]. mixed is multipart/mixed [text/plain, attachments…]. nested is multipart/mixed [multipart/alternative [text/plain, text/html], attachments…]. attachments above 0 therefore requires mixed or nested; asking for one on plain or alternative is a 400 rather than a silently promoted structure.

unicode=true switches to display names and subjects that genuinely contain non-ASCII (Latin diacritics, Cyrillic, CJK, one emoji) so From, To, Cc and Subject carry RFC 2047 B encoded-words. Local parts and body text stay US-ASCII — this API never emits SMTPUTF8 addresses, and the whole raw message is pure 7-bit ASCII.

Note on names: the from and to query parameters bound the date window; the from and to fields in each record are mailboxes. Supply exactly one bound and the other resolves 30 days away, so either works on its own. YYYY-MM-DD means midnight UTC — pass a timestamp to cover that whole day — and because an RFC 5322 Date carries no fraction of a second, a window that contains no whole second (say 10:00:00.200Z10:00:00.800Z) is a 400 rather than a rounded guess.

What this is not

  • Nothing here can be delivered. Every address, Message-ID and Content-ID domain is an RFC 2606 reserved name — example.com, example.net, example.org, or a label under the reserved .test TLD. These fixtures cannot be used to seed a real send.
  • Attachment payloads are generated bytes. export.csv and notes.txt really are CSV and plain text, but chart.png, scan.jpg, q3-report.pdf and logs.zip are filler octets with the right Content-Type and a sizeBytes that matches the decoded length. They will not open in an image or PDF viewer.
  • No client quirks are reproduced. Outlook, Gmail and Apple Mail all deviate from the RFCs in ways we cannot verify, so we make no claim about matching any of them. What you get is what the specifications say.
  • This generates messages; it does not parse yours. There is no upload route. The point is that a synthetic source removes the usual alternative — finding a real .eml and redacting it — along with its privacy risk.
  • Out of scope: multipart/related and cid: image references, S/MIME and PGP, message/rfc822 forwarding, DKIM/SPF/Authentication-Results headers, trace headers (Received), and 8-bit or binary transfer encodings. Attachment parts do carry a Content-ID (RFC 2045 §7) because real clients emit one, but nothing in the HTML body references it with a cid: URL — that would need multipart/related.
  • The message has no trailing CRLF. RFC 5322's body grammar allows a final line without one, and sizeBytes counts exactly what raw contains; add a newline yourself if your tooling expects a POSIX-style text file.

Use it for

  • Test a MIME parser or a threading algorithm without redacting somebody's real .eml file
  • Fill an inbox or ticketing UI with messages that have real part trees, attachments and encoded-word subjects
  • Check that a mail pipeline handles 7bit, quoted-printable and base64 bodies identically
  • Generate a reply chain whose References header is a valid ancestor chain, to verify thread grouping

Frequently asked questions

How do I get a sample .eml file to test my parser?

Call /api/email-messages/raw and save the raw field verbatim as sample.eml. It is a complete RFC 5322 message in pure US-ASCII with CRLF line endings, and the same query returns byte-identical output every time — safe to commit as a golden file.

What exactly goes in In-Reply-To and References?

In-Reply-To holds exactly the parent's Message-ID. References holds the parent's References followed by the parent's Message-ID, so it reads root first and ends with the direct parent (RFC 5322 §3.6.4). /thread?depth=8 returns the whole chain so you can assert against it.

Can these messages be delivered or used to send mail?

No. Every address, Message-ID and Content-ID domain is an RFC 2606 reserved name — example.com/.net/.org or a .test label — so nothing resolves to a real mail server and no fixture can seed a real send.

Do the attachments contain real files?

No. export.csv and notes.txt are genuine CSV and plain text, but the PNG, JPEG, PDF and ZIP parts are generated octets with the correct Content-Type and a sizeBytes that matches the decoded length. They are base64-correct but will not open in a viewer.

Does this reproduce Outlook or Gmail quirks?

No, and we do not claim to. Outlook, Gmail and Apple Mail each deviate from the RFCs in ways we cannot verify; these fixtures follow the specifications, and pretending otherwise would be dishonest.

Is the Date header ISO 8601?

No — headers.Date is an RFC 5322 date-time such as Tue, 21 Jul 2026 14:03:11 +0200, including a real UTC offset. The date field carries the same instant in ISO 8601 UTC so you can compare the two.

Standards & references