From c6d86b74d2ef012374b131e6f406c9ecbbce2114 Mon Sep 17 00:00:00 2001 From: Marcin Klocek Date: Sat, 18 Jul 2026 15:36:24 +0200 Subject: [PATCH 1/7] Document inbound reply, reply_all, and forward endpoints --- specs/inbound.openapi.yml | 359 +++++++++++++++++++++++++++++++++++++- 1 file changed, 357 insertions(+), 2 deletions(-) diff --git a/specs/inbound.openapi.yml b/specs/inbound.openapi.yml index 9385d7e..2e6089e 100644 --- a/specs/inbound.openapi.yml +++ b/specs/inbound.openapi.yml @@ -46,11 +46,12 @@ tags: - name: messages x-page-title: Messages - x-page-description: List and inspect received messages + x-page-description: List, inspect, and respond to received messages description: | Inbound messages are the emails delivered to an inbox. Use these endpoints to page through recent messages, fetch the full body and - attachments, or delete a message you no longer need. + attachments, reply to or forward a message, or delete one you no longer + need. paths: /api/inbound/folders: @@ -527,6 +528,168 @@ paths: "404": $ref: "#/components/responses/NotFound" + /api/inbound/inboxes/{inbox_id}/messages/{id}/reply: + post: + operationId: replyToInboundMessage + summary: Reply to a message + description: | + Send a reply to an inbound message. The reply is threaded to the + original and its subject is prefixed with `Re:` (unless it already + starts with one). + + By default the reply is addressed to the original sender (its + `Reply-To`, or `From` when absent). Pass `to` to override the + recipients — an explicit empty array sends to `cc`/`bcc` only — or add + `cc`/`bcc`. + + The sender depends on the inbox type: + + - **Mailtrap-hosted inbox** — always sends from the inbox's own + generated address. Supplying `from` is rejected with `400`. + - **Custom-domain inbox** — you supply `from`, whose address must + belong to the inbox's domain. + tags: + - messages + parameters: + - $ref: "#/components/parameters/inbox_id" + - $ref: "#/components/parameters/message_id" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SendMessageInput" + example: + text: "Thanks for reaching out. We are looking into it." + html: "

Thanks for reaching out. We are looking into it.

" + x-codeSamples: + - lang: shell + label: "cURL" + source: | + curl -X POST https://mailtrap.io/api/inbound/inboxes/{inbox_id}/messages/{id}/reply \ + -H 'Authorization: Bearer YOUR_API_KEY' \ + -H 'Content-Type: application/json' \ + -d '{ "text": "Thanks for reaching out. We are looking into it." }' + responses: + "201": + $ref: "#/components/responses/SendMessageAccepted" + "400": + $ref: "#/components/responses/SendMessageBadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/SendMessageForbidden" + "404": + $ref: "#/components/responses/NotFound" + "413": + $ref: "#/components/responses/PayloadTooLarge" + "429": + $ref: "#/components/responses/TooManyRequests" + + /api/inbound/inboxes/{inbox_id}/messages/{id}/reply_all: + post: + operationId: replyAllToInboundMessage + summary: Reply all to a message + description: | + Send a reply to an inbound message and copy the original's other + recipients. Behaves exactly like the Reply to a message operation, + except `cc` defaults to the original's `To` + `Cc` — minus the inbox's + own addresses and the sender (already in `To`). + + Pass `to`, `cc`, or `bcc` to override the computed recipients. As with a + plain reply, the subject is prefixed with `Re:`. + tags: + - messages + parameters: + - $ref: "#/components/parameters/inbox_id" + - $ref: "#/components/parameters/message_id" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SendMessageInput" + example: + text: "Thanks all. Adding my colleague to the thread." + x-codeSamples: + - lang: shell + label: "cURL" + source: | + curl -X POST https://mailtrap.io/api/inbound/inboxes/{inbox_id}/messages/{id}/reply_all \ + -H 'Authorization: Bearer YOUR_API_KEY' \ + -H 'Content-Type: application/json' \ + -d '{ "text": "Thanks all. Adding my colleague to the thread." }' + responses: + "201": + $ref: "#/components/responses/SendMessageAccepted" + "400": + $ref: "#/components/responses/SendMessageBadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/SendMessageForbidden" + "404": + $ref: "#/components/responses/NotFound" + "413": + $ref: "#/components/responses/PayloadTooLarge" + "429": + $ref: "#/components/responses/TooManyRequests" + + /api/inbound/inboxes/{inbox_id}/messages/{id}/forward: + post: + operationId: forwardInboundMessage + summary: Forward a message + description: | + Forward an inbound message to new recipients. The subject is prefixed + with `Fwd:` (unless it already starts with one). The original message is + always carried along: its rendered body is quoted below your optional + note and its attachments are re-attached (any attachments you supply are + added on top). + + A forward has no implicit recipient — you must address it with `to` + (and optionally `cc`/`bcc`). The `from` rules match those of the Reply operation: + rejected for Mailtrap-hosted inboxes, required-and-domain-bound for + custom-domain inboxes. + tags: + - messages + parameters: + - $ref: "#/components/parameters/inbox_id" + - $ref: "#/components/parameters/message_id" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SendMessageInput" + example: + to: + - email: "colleague@example.com" + name: "A. Colleague" + text: "Please take a look at the message below." + x-codeSamples: + - lang: shell + label: "cURL" + source: | + curl -X POST https://mailtrap.io/api/inbound/inboxes/{inbox_id}/messages/{id}/forward \ + -H 'Authorization: Bearer YOUR_API_KEY' \ + -H 'Content-Type: application/json' \ + -d '{ "to": [{ "email": "colleague@example.com" }], "text": "Please take a look at the message below." }' + responses: + "201": + $ref: "#/components/responses/SendMessageAccepted" + "400": + $ref: "#/components/responses/SendMessageBadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/SendMessageForbidden" + "404": + $ref: "#/components/responses/NotFound" + "413": + $ref: "#/components/responses/PayloadTooLarge" + "429": + $ref: "#/components/responses/TooManyRequests" + components: securitySchemes: HeaderAuth: @@ -829,6 +992,141 @@ components: Cursor for the next page. `null` when there are no more results. example: null + Address: + type: object + required: + - email + properties: + email: + type: string + format: email + example: "recipient@example.com" + name: + type: string + example: "Jane Doe" + + EmailAttachment: + type: object + required: + - content + - filename + properties: + content: + type: string + description: Base64-encoded attachment content. + example: "PGh0bWw+PGJvZHk+SGVsbG88L2JvZHk+PC9odG1sPg==" + filename: + type: string + example: "invoice.pdf" + type: + type: string + description: MIME type of the attachment. + example: "application/pdf" + disposition: + type: string + enum: + - attachment + - inline + default: attachment + example: attachment + content_id: + type: string + description: | + `Content-ID` used to reference an inline attachment from the HTML + body. Only relevant when `disposition` is `inline`. + example: "invoice@example.com" + + SendMessageInput: + type: object + description: | + Options for the sent message. All fields are optional. `reply` and + `reply_all` must carry a body (`text` and/or `html`); `forward` may omit + it, since the original message is quoted automatically. `forward` also + requires at least one recipient in `to`. Addresses use the Email + Sending API `{ email, name }` shape. + properties: + from: + allOf: + - $ref: "#/components/schemas/Address" + description: | + Sender address. Rejected for Mailtrap-hosted inboxes (they always + send from their own address); required for custom-domain inboxes, + where the address must belong to the inbox's domain. + to: + type: array + maxItems: 1000 + items: + $ref: "#/components/schemas/Address" + cc: + type: array + maxItems: 1000 + items: + $ref: "#/components/schemas/Address" + bcc: + type: array + maxItems: 1000 + items: + $ref: "#/components/schemas/Address" + reply_to: + $ref: "#/components/schemas/Address" + text: + type: string + description: Plain-text body. + example: "Thanks for reaching out. We are looking into it." + html: + type: string + description: HTML body. + example: "

Thanks for reaching out. We are looking into it.

" + category: + type: string + maxLength: 255 + description: Email API category for the sent message. + example: "Support reply" + attachments: + type: array + items: + $ref: "#/components/schemas/EmailAttachment" + headers: + type: object + additionalProperties: + type: string + description: Custom headers to add to the sent message. + example: + X-Custom-Header: "value" + custom_variables: + type: object + additionalProperties: + type: string + description: Email API custom variables for the sent message. + example: + ticket_id: "12345" + + SendMessageResult: + type: object + required: + - message_ids + properties: + message_ids: + type: array + description: | + UUIDs assigned by the Email API to the sent message, one per + recipient. These are the sent message's UUIDs — not the inbound + message ID acted on, nor an RFC `Message-ID` header value. + items: + type: string + example: + - "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" + + SendMessageError: + type: object + properties: + errors: + oneOf: + - type: string + - type: array + items: + type: string + UnprocessableEntity: type: object properties: @@ -889,3 +1187,60 @@ components: errors: name: - has already been taken + + SendMessageAccepted: + description: | + Message queued for delivery. Returns the UUID(s) the Email API + assigned to the sent message, one per recipient. + content: + application/json: + schema: + $ref: "#/components/schemas/SendMessageResult" + example: + message_ids: + - "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" + + SendMessageBadRequest: + description: | + The message could not be sent. Returned for an invalid sender (for + example a `from` on a Mailtrap-hosted inbox, or one outside the inbox's + domain) and for validation errors rejected by the Email API. + content: + application/json: + schema: + $ref: "#/components/schemas/SendMessageError" + example: + errors: + - "'from' must be an address on the inbox's domain" + + SendMessageForbidden: + description: | + The caller lacks permission on the inbox, or the inbox's monthly sending + limit has been reached. + content: + application/json: + schema: + $ref: "#/components/schemas/SendMessageError" + example: + errors: + - "Sending usage of this inbox has reached its limit." + + PayloadTooLarge: + description: The message exceeds the maximum allowed size. + content: + application/json: + schema: + $ref: "#/components/schemas/SendMessageError" + example: + errors: + - "Message is too large" + + TooManyRequests: + description: Too many requests. Retry after a short delay. + content: + application/json: + schema: + $ref: "#/components/schemas/SendMessageError" + example: + errors: + - "Too many requests" From 56c126e0beaf1175babbb7addacbc6565d5cf6c0 Mon Sep 17 00:00:00 2001 From: Marcin Klocek Date: Sat, 18 Jul 2026 15:42:26 +0200 Subject: [PATCH 2/7] Document inbound threads endpoints List, show, and delete. The list response uses the same cursor envelope as inbound messages ({ data, total_count, last_id }); the show response returns the thread at the root. --- specs/inbound.openapi.yml | 469 +++++++++++++++++++++++++++++++++++++- 1 file changed, 466 insertions(+), 3 deletions(-) diff --git a/specs/inbound.openapi.yml b/specs/inbound.openapi.yml index 2e6089e..e23f1aa 100644 --- a/specs/inbound.openapi.yml +++ b/specs/inbound.openapi.yml @@ -53,6 +53,15 @@ tags: attachments, reply to or forward a message, or delete one you no longer need. + - name: threads + x-page-title: Threads + x-page-description: Group related messages into conversations + description: | + A thread groups the inbound and sent messages that belong to the + same conversation. Use these endpoints to page through an inbox's + threads, fetch a single thread with its messages embedded, or delete a + thread. + paths: /api/inbound/folders: get: @@ -690,6 +699,220 @@ paths: "429": $ref: "#/components/responses/TooManyRequests" + /api/inbound/inboxes/{inbox_id}/threads: + get: + operationId: listInboundThreads + summary: List threads + description: | + Returns the inbox's conversation threads, ordered by most recent + activity first. + + Only threads and messages within your plan's retention window are + included; the per-thread aggregates (message count, size, senders, + recipients, attachments) are computed over those accessible messages. + + Results use cursor pagination. When more results are available the + response includes a `last_id`; pass it back as the `last_id` query + parameter to get the next page. When `last_id` is `null` you have + reached the end. + tags: + - threads + parameters: + - $ref: "#/components/parameters/inbox_id" + - name: last_id + in: query + required: false + description: | + Cursor from the previous response's `last_id`. Omit on the first + request. + schema: + type: string + example: "eyJsYXN0X2FjdGl2aXR5X2F0IjoxNzQ2NzAwMjAwMDAwLCJpZCI6IjE3MDAwMDAwMDAwMDAxMjMifQ" + x-codeSamples: + - lang: shell + label: "cURL" + source: | + curl -X GET 'https://mailtrap.io/api/inbound/inboxes/{inbox_id}/threads' \ + -H 'Authorization: Bearer YOUR_API_KEY' + responses: + "200": + description: List of threads + content: + application/json: + schema: + $ref: "#/components/schemas/ThreadsListResponse" + example: + data: + - id: "1700000000000123" + subject: "Support request" + message_count: 3 + size: 24576 + first_message_at: "2026-05-08T10:30:00.000Z" + last_received_at: "2026-05-08T12:05:00.000Z" + last_sent_at: "2026-05-08T11:40:00.000Z" + last_activity_at: "2026-05-08T12:05:00.000Z" + last_message_id: "1700000000000789" + senders: + - "customer@example.com" + - "support-tickets-1a2b3c4d@inbound-mailtrap.io" + recipients: + - "support-tickets-1a2b3c4d@inbound-mailtrap.io" + - "customer@example.com" + attachments: + - attachment_id: att-1 + filename: invoice.pdf + size: 20480 + content_type: application/pdf + content_disposition: attachment + content_id: null + download_url: "https://s3.amazonaws.com/inbound-mail/att-1?X-Amz-Signature=..." + download_url_expires_at: "2026-05-08T13:05:00.000Z" + total_count: 1 + last_id: null + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + + /api/inbound/inboxes/{inbox_id}/threads/{id}: + get: + operationId: getInboundThread + summary: Get a thread + description: | + Returns a single thread with its messages embedded, ordered oldest + first. + + Each message carries a `visibility_status`: + + - `available` — the message is within your plan's retention window. The + full envelope, subject, RFC headers, decoded bodies, and attachment + download URLs are included; sent messages also carry their + delivery lifecycle (`delivery_status`, `delivered_at`, `bounced_at`). + - `placeholder` — the message is outside the retention window. Only + `visibility_status` and `direction` are returned; every other field is + omitted and no attachment download URLs are issued. + tags: + - threads + parameters: + - $ref: "#/components/parameters/inbox_id" + - $ref: "#/components/parameters/thread_id" + x-codeSamples: + - lang: shell + label: "cURL" + source: | + curl -X GET https://mailtrap.io/api/inbound/inboxes/{inbox_id}/threads/{id} \ + -H 'Authorization: Bearer YOUR_API_KEY' + responses: + "200": + description: Thread details + content: + application/json: + schema: + $ref: "#/components/schemas/Thread" + example: + id: "1700000000000123" + subject: "Support request" + message_count: 3 + size: 24576 + first_message_at: "2026-05-08T10:30:00.000Z" + last_received_at: "2026-05-08T12:05:00.000Z" + last_sent_at: "2026-05-08T11:40:00.000Z" + last_activity_at: "2026-05-08T12:05:00.000Z" + last_message_id: "1700000000000789" + senders: + - "customer@example.com" + - "support-tickets-1a2b3c4d@inbound-mailtrap.io" + recipients: + - "support-tickets-1a2b3c4d@inbound-mailtrap.io" + - "customer@example.com" + attachments: + - attachment_id: att-1 + filename: invoice.pdf + size: 20480 + content_type: application/pdf + content_disposition: attachment + content_id: null + download_url: "https://s3.amazonaws.com/inbound-mail/att-1?X-Amz-Signature=..." + download_url_expires_at: "2026-05-08T13:05:00.000Z" + messages: + - visibility_status: available + direction: inbound + id: "1700000000000123" + subject: "Support request" + message_id: "" + in_reply_to: null + references: [] + from: "customer@example.com" + to: + - "support-tickets-1a2b3c4d@inbound-mailtrap.io" + cc: [] + bcc: [] + reply_to: null + created_at: "2026-05-08T10:30:00.000Z" + email_size: 8192 + text_body: "I need help with my order." + html_body: "

I need help with my order.

" + attachments: [] + - visibility_status: available + direction: outbound + id: "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" + subject: "Re: Support request" + message_id: "" + in_reply_to: "" + references: + - "" + from: "support-tickets-1a2b3c4d@inbound-mailtrap.io" + to: + - "customer@example.com" + cc: [] + bcc: [] + reply_to: null + created_at: "2026-05-08T11:40:00.000Z" + email_size: 8192 + text_body: "Happy to help — could you share your order ID?" + html_body: "

Happy to help — could you share your order ID?

" + attachments: [] + delivery_status: delivered + delivered_at: "2026-05-08T11:40:05.000Z" + bounced_at: null + - visibility_status: placeholder + direction: inbound + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + + delete: + operationId: deleteInboundThread + summary: Delete a thread + description: | + Delete a thread. Inbound messages in the thread are removed; sent + messages are preserved. + tags: + - threads + parameters: + - $ref: "#/components/parameters/inbox_id" + - $ref: "#/components/parameters/thread_id" + x-codeSamples: + - lang: shell + label: "cURL" + source: | + curl -X DELETE https://mailtrap.io/api/inbound/inboxes/{inbox_id}/threads/{id} \ + -H 'Authorization: Bearer YOUR_API_KEY' + responses: + "204": + description: Thread deleted + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + components: securitySchemes: HeaderAuth: @@ -731,6 +954,15 @@ components: type: string example: "1700000000000123" + thread_id: + name: id + in: path + required: true + description: Inbound thread ID + schema: + type: string + example: "1700000000000123" + schemas: Folder: type: object @@ -828,7 +1060,7 @@ components: - string - "null" format: uri - description: Presigned URL to download the attachment. Expires after one hour. + description: URL to download the attachment. Expires after one hour. example: "https://s3.amazonaws.com/inbound-mail/att-1?X-Amz-Signature=..." download_url_expires_at: type: @@ -947,8 +1179,7 @@ components: - "null" format: uri description: | - Presigned URL to download the raw `.eml` file. Expires after - one hour. + URL to download the raw `.eml` file. Expires after one hour. example: "https://s3.amazonaws.com/inbound-mail/raw.eml?X-Amz-Signature=..." raw_message_expires_at: type: @@ -1127,6 +1358,238 @@ components: items: type: string + ThreadSummary: + type: object + description: | + Summary of a thread. All aggregates are computed over the messages you + can still access (those within the plan's retention window). + properties: + id: + type: string + example: "1700000000000123" + subject: + type: + - string + - "null" + description: Subject of the thread's root message. + example: "Support request" + message_count: + type: integer + description: | + Number of accessible messages in the thread. Messages sent together + (reply-all fan-out) count once. + example: 3 + size: + type: integer + description: Combined size of the accessible messages, in bytes. + example: 24576 + first_message_at: + type: string + format: date-time + example: "2026-05-08T10:30:00.000Z" + last_received_at: + type: + - string + - "null" + format: date-time + description: When the most recent inbound message arrived, if any. + example: "2026-05-08T12:05:00.000Z" + last_sent_at: + type: + - string + - "null" + format: date-time + description: When the most recent message was sent, if any. + example: "2026-05-08T11:40:00.000Z" + last_activity_at: + type: string + format: date-time + description: Timestamp of the most recent message in either direction. + example: "2026-05-08T12:05:00.000Z" + last_message_id: + type: + - string + - "null" + description: ID of the most recent accessible message in the thread. + example: "1700000000000789" + senders: + type: array + description: Distinct `from` addresses across the accessible messages. + items: + type: string + example: + - "customer@example.com" + - "support-tickets-1a2b3c4d@inbound-mailtrap.io" + recipients: + type: array + description: Distinct `to`/`cc`/`bcc` addresses across the accessible messages. + items: + type: string + example: + - "support-tickets-1a2b3c4d@inbound-mailtrap.io" + - "customer@example.com" + attachments: + type: array + description: Attachments across the accessible messages, with download URLs. + items: + $ref: "#/components/schemas/AttachmentWithDownloadUrl" + + ThreadMessage: + type: object + description: | + A message in a thread. `placeholder` entries (outside the retention + window) carry only `visibility_status` and `direction`. `available` + entries add the full envelope, subject, RFC headers, bodies, and + attachments; `available` sent entries additionally carry the + delivery lifecycle. + required: + - visibility_status + - direction + properties: + visibility_status: + type: string + enum: + - available + - placeholder + example: available + direction: + type: string + enum: + - inbound + - outbound + example: inbound + id: + type: string + description: | + For inbound messages, the Mailtrap object ID. For sent messages, + the Email API message UUID. + example: "1700000000000123" + subject: + type: + - string + - "null" + example: "Support request" + message_id: + type: + - string + - "null" + description: Value of the `Message-ID` header. + example: "" + in_reply_to: + type: + - string + - "null" + example: null + references: + type: array + items: + type: string + example: [] + from: + type: + - string + - "null" + example: "customer@example.com" + to: + type: array + items: + type: string + example: + - "support-tickets-1a2b3c4d@inbound-mailtrap.io" + cc: + type: array + items: + type: string + example: [] + bcc: + type: array + items: + type: string + example: [] + reply_to: + type: + - string + - "null" + example: null + created_at: + type: string + format: date-time + example: "2026-05-08T10:30:00.000Z" + email_size: + type: integer + description: Size of the message in bytes. + example: 8192 + text_body: + type: + - string + - "null" + description: Decoded plain-text body. `null` when the message has no text part. + example: "I need help with my order." + html_body: + type: + - string + - "null" + description: Decoded HTML body. `null` when the message has no HTML part. + example: "

I need help with my order.

" + attachments: + type: array + items: + $ref: "#/components/schemas/AttachmentWithDownloadUrl" + delivery_status: + type: + - string + - "null" + description: Delivery status. Sent messages only. + example: delivered + delivered_at: + type: + - string + - "null" + format: date-time + description: When the message was delivered. Sent messages only. + example: "2026-05-08T11:40:05.000Z" + bounced_at: + type: + - string + - "null" + format: date-time + description: When the message hard-bounced, if it did. Sent messages only. + example: null + + Thread: + allOf: + - $ref: "#/components/schemas/ThreadSummary" + - type: object + properties: + messages: + type: array + description: The thread's messages, ordered oldest first. + items: + $ref: "#/components/schemas/ThreadMessage" + + ThreadsListResponse: + type: object + required: + - data + - total_count + - last_id + properties: + data: + type: array + items: + $ref: "#/components/schemas/ThreadSummary" + total_count: + type: integer + description: Total number of threads matching the query. + example: 1 + last_id: + type: + - string + - "null" + description: | + Cursor for the next page. `null` when there are no more results. + example: null + UnprocessableEntity: type: object properties: From 38f8529108d54d89f173e301232a12febb4ba076 Mon Sep 17 00:00:00 2001 From: Marcin Klocek Date: Sat, 18 Jul 2026 15:44:44 +0200 Subject: [PATCH 3/7] Document thread_id and message_group_id thread_id links an inbound message to its thread; message_group_id groups a thread message with the reply it belongs to. --- specs/inbound.openapi.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/specs/inbound.openapi.yml b/specs/inbound.openapi.yml index e23f1aa..aa8d383 100644 --- a/specs/inbound.openapi.yml +++ b/specs/inbound.openapi.yml @@ -461,6 +461,7 @@ paths: html_size: 512 text_size: 128 received_at: "2026-05-08T10:30:00.000Z" + thread_id: "1700000000000124" attachments: - attachment_id: att-1 size: 1024 @@ -1159,6 +1160,14 @@ components: type: string format: date-time example: "2026-05-08T10:30:00.000Z" + thread_id: + type: + - string + - "null" + description: | + ID of the thread this message belongs to. Use it with the Threads + endpoints to fetch the full conversation. + example: "1700000000000124" attachments: type: array items: @@ -1464,6 +1473,16 @@ components: For inbound messages, the Mailtrap object ID. For sent messages, the Email API message UUID. example: "1700000000000123" + message_group_id: + type: + - string + - "null" + description: | + Groups the copies of one logical message that was fanned out to + multiple recipients (e.g. a reply-all). Messages sharing a + `message_group_id` count once toward the thread's `message_count`. + `null` for a standalone message. + example: null subject: type: - string From 62a14accda49ee49092f2d335962d9760a7f67b5 Mon Sep 17 00:00:00 2001 From: Marcin Klocek Date: Sat, 18 Jul 2026 15:45:14 +0200 Subject: [PATCH 4/7] Rename the inbound RFC Message-ID field to rfc_message_id Exposes the message's RFC 5322 Message-ID header as rfc_message_id on message and thread-message responses, avoiding a name collision with the Email Log message_id (the message UUID). --- specs/inbound.openapi.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/inbound.openapi.yml b/specs/inbound.openapi.yml index aa8d383..efcb3a7 100644 --- a/specs/inbound.openapi.yml +++ b/specs/inbound.openapi.yml @@ -452,7 +452,7 @@ paths: bcc: [] reply_to: null subject: Hello - message_id: "" + rfc_message_id: "" in_reply_to: null references: [] headers: @@ -842,7 +842,7 @@ paths: direction: inbound id: "1700000000000123" subject: "Support request" - message_id: "" + rfc_message_id: "" in_reply_to: null references: [] from: "customer@example.com" @@ -860,7 +860,7 @@ paths: direction: outbound id: "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" subject: "Re: Support request" - message_id: "" + rfc_message_id: "" in_reply_to: "" references: - "" @@ -1111,7 +1111,7 @@ components: - string - "null" example: Hello - message_id: + rfc_message_id: type: - string - "null" @@ -1488,7 +1488,7 @@ components: - string - "null" example: "Support request" - message_id: + rfc_message_id: type: - string - "null" From fa08fb064e45c3fa446ef2595d92e7ba2383124d Mon Sep 17 00:00:00 2001 From: Marcin Klocek Date: Sat, 18 Jul 2026 15:47:58 +0200 Subject: [PATCH 5/7] Document inbound custom-domain support Adds inbound_enabled/inbound_verified on the sending domain and the domain_id/catch-all inbox flow (InboxCreateInput, *@your-domain.com addresses). --- specs/email-sending.openapi.yml | 29 ++++++++++++++- specs/inbound.openapi.yml | 66 +++++++++++++++++++++++++++------ 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/specs/email-sending.openapi.yml b/specs/email-sending.openapi.yml index 5b3ff76..13bb064 100644 --- a/specs/email-sending.openapi.yml +++ b/specs/email-sending.openapi.yml @@ -579,7 +579,8 @@ paths: "open_tracking_enabled": true, "click_tracking_enabled": true, "tracking_opt_out_enabled": true, - "auto_unsubscribe_link_enabled": false + "auto_unsubscribe_link_enabled": false, + "inbound_enabled": true } }' parameters: @@ -601,6 +602,7 @@ paths: click_tracking_enabled: true tracking_opt_out_enabled: true auto_unsubscribe_link_enabled: false + inbound_enabled: true responses: '200': $ref: '#/components/responses/DomainResponse' @@ -2403,6 +2405,17 @@ components: type: - string - 'null' + inbound_enabled: + type: boolean + description: | + Whether inbound email is enabled for this domain. When enabled, an + `MX` record (`key: inbound_mx`) is added to `dns_records` for inbound + mail; see `inbound_verified`. + inbound_verified: + type: boolean + description: | + Whether the domain's inbound MX records have been verified. Inbound + mail is only accepted once this is `true`. permissions: type: object properties: @@ -2456,6 +2469,12 @@ components: value: t.mailtrap.live status: pass name: mt-link + - key: inbound_mx + domain: mailtrap.io + type: MX + value: inbound-mailtrap.io + status: pass + name: '@' open_tracking_enabled: true click_tracking_enabled: true tracking_opt_out_enabled: false @@ -2464,6 +2483,8 @@ components: health_alerts_enabled: true critical_alerts_enabled: true alert_recipient_email: john.doe@mailtrap.io + inbound_enabled: true + inbound_verified: true permissions: can_read: true can_update: true @@ -2486,6 +2507,12 @@ components: auto_unsubscribe_link_enabled: type: boolean description: Automatically add an unsubscribe link to emails sent from this domain + inbound_enabled: + type: boolean + description: | + Enable inbound email for this domain so it can be attached to an + inbound inbox as a catch-all. The domain's inbound MX records must + be verified before inbound mail is accepted (see `inbound_verified`). CompanyInfo: type: object diff --git a/specs/inbound.openapi.yml b/specs/inbound.openapi.yml index efcb3a7..8dcdd60 100644 --- a/specs/inbound.openapi.yml +++ b/specs/inbound.openapi.yml @@ -252,9 +252,11 @@ paths: - id: 1 name: Support tickets address: "support-tickets-1a2b3c4d@inbound-mailtrap.io" + domain_id: 892 - id: 2 name: Replies - address: "replies-5e6f7a8b@inbound-mailtrap.io" + address: "*@your-domain.com" + domain_id: 894 "401": $ref: "#/components/responses/Unauthorized" "403": @@ -266,8 +268,13 @@ paths: operationId: createInboundInbox summary: Create an inbox description: | - Create a new inbox in a folder. Mailtrap generates the inbox `address` - automatically based on the `name`. + Create a new inbox in a folder. By default Mailtrap generates the + inbox `address` automatically based on the `name`. + + To create a catch-all inbox on your own domain, pass `domain_id` — the + ID of a verified custom sending domain with inbound enabled. The inbox + then receives mail sent to any address on that domain, and its `address` + is returned as `*@your-domain.com`. tags: - inboxes parameters: @@ -285,7 +292,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/InboxInput" + $ref: "#/components/schemas/InboxCreateInput" responses: "201": description: Inbox created @@ -293,10 +300,21 @@ paths: application/json: schema: $ref: "#/components/schemas/Inbox" - example: - id: 1 - name: Support tickets - address: "support-tickets-1a2b3c4d@inbound-mailtrap.io" + examples: + mailtrapHosted: + summary: Mailtrap-hosted inbox + value: + id: 1 + name: Support tickets + address: "support-tickets-1a2b3c4d@inbound-mailtrap.io" + domain_id: 892 + customDomain: + summary: Custom-domain (catch-all) inbox + value: + id: 2 + name: Support tickets + address: "*@your-domain.com" + domain_id: 894 "401": $ref: "#/components/responses/Unauthorized" "403": @@ -996,11 +1014,19 @@ components: example: Support tickets address: type: string - format: email description: | - Generated inbound address. Mail sent to this address is delivered - to the inbox. + The inbox's inbound address. For a standard Mailtrap-hosted inbox + this is a unique generated address. For a custom-domain inbox it is + the catch-all form `*@your-domain.com`, which receives mail sent to + any address on that domain. example: "support-tickets-1a2b3c4d@inbound-mailtrap.io" + domain_id: + type: integer + description: | + ID of the sending domain the inbox is attached to. For a + custom-domain inbox this is your own sending domain; for a standard + hosted inbox it is the Mailtrap-managed domain backing the inbox. + example: 892 InboxInput: type: object @@ -1012,6 +1038,24 @@ components: maxLength: 100 example: Support tickets + InboxCreateInput: + type: object + required: + - name + properties: + name: + type: string + maxLength: 100 + example: Support tickets + domain_id: + type: integer + description: | + Attach the inbox to a verified custom sending domain (one with + inbound enabled), creating a catch-all inbox that receives mail for + any address on that domain. Omit to create a standard Mailtrap-hosted + inbox. Requires the custom domains feature. + example: 894 + Attachment: type: object properties: From 2546953cdbc4f37fbdb3d306cff499bf092ea4cb Mon Sep 17 00:00:00 2001 From: Marcin Klocek Date: Sat, 18 Jul 2026 15:49:20 +0200 Subject: [PATCH 6/7] Document RFC threading headers on the Email Log API Adds rfc_message_id, in_reply_to, and references to the Email Log message and list schemas. --- specs/email-sending.openapi.yml | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/specs/email-sending.openapi.yml b/specs/email-sending.openapi.yml index 13bb064..c10336d 100644 --- a/specs/email-sending.openapi.yml +++ b/specs/email-sending.openapi.yml @@ -2961,6 +2961,26 @@ components: subject: type: string nullable: true + rfc_message_id: + type: string + nullable: true + description: Value of the RFC 5322 `Message-ID` header. + in_reply_to: + type: string + nullable: true + description: Value of the RFC 5322 `In-Reply-To` header. + references: + type: array + items: + type: string + description: Values of the RFC 5322 `References` header. + thread_id: + type: string + nullable: true + description: | + ID of the inbound thread this message belongs to, or null if + the message is not part of one. Use it with the Inbound + Threads endpoints to fetch the full conversation. from: type: string to: @@ -3045,6 +3065,26 @@ components: subject: type: string nullable: true + rfc_message_id: + type: string + nullable: true + description: Value of the RFC 5322 `Message-ID` header. + in_reply_to: + type: string + nullable: true + description: Value of the RFC 5322 `In-Reply-To` header. + references: + type: array + items: + type: string + description: Values of the RFC 5322 `References` header. + thread_id: + type: string + nullable: true + description: | + ID of the inbound thread this message belongs to, or null if the + message is not part of one. Use it with the Inbound Threads + endpoints to fetch the full conversation. from: type: string to: From 864e67622a55a13105b8fd52dd0a9c0769963920 Mon Sep 17 00:00:00 2001 From: Marcin Klocek Date: Mon, 20 Jul 2026 15:04:34 +0200 Subject: [PATCH 7/7] Fix sending-domain DNS records example: drop SPF record SPF is set on the HELO domain, not per sending domain, so the domains API's dns_records never includes an SPF entry. --- specs/email-sending.openapi.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/specs/email-sending.openapi.yml b/specs/email-sending.openapi.yml index c10336d..61a7f8b 100644 --- a/specs/email-sending.openapi.yml +++ b/specs/email-sending.openapi.yml @@ -2439,12 +2439,6 @@ components: value: smtp.mailtrap.live status: pass name: ve6wza2rbpe60x7z - - key: spf - domain: mailtrap.io - type: TXT - value: 'v=spf1 include:_spf.smtp.mailtrap.live ~all' - status: pass - name: '' - key: dkim1 domain: rwmt1._domainkey.mailtrap.io type: CNAME