openmail-sh Messages API

Sending and listing messages

Operations 4

POST /v1/inboxes/{id}/send Send email #
GET /v1/inboxes/{id}/messages List messages #
GET /v1/messages/{id}/raw Download raw message #
DELETE /v1/messages/{id} Delete message #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/openmail-sh:openmail-sh-messages-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

openmail-sh-messages-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: OpenMail Messages API
  description: Email infrastructure API for AI agents. Create inboxes, send and receive email, and get real-time notifications via webhooks.
  version: 1.0.0
  license:
    name: Proprietary
servers:
- url: https://api.openmail.sh
security:
- bearerAuth: []
tags:
- name: Messages
  description: Sending and listing messages
paths:
  /v1/inboxes/{id}/send:
    post:
      operationId: sendEmail
      summary: Send email
      tags:
      - Messages
      description: 'Send an email from an inbox. Optionally pass an Idempotency-Key header to make retries safe: a repeat with the same key within 24 hours returns the original message instead of sending again. Without it the API generates a key and sends once. Use threadId to reply to an existing thread. When replying on a thread where your inbox was CC''d (deliveryRole cc), the original sender is automatically CC''d.


        Every recipient this call can reach — `to`, `cc` (including any address auto-CC''d on a thread reply), `bcc`, and `replyTo` — is checked against the inbox''s outbound correspondent policy before anything is sent. A single denied recipient rejects the whole send with 403 `recipient_not_permitted`; there is no partial delivery.


        `bcc` recipients are delivered and stored on the outbound message, but they are not listed on the copy To and Cc recipients receive. An address already in `to` or `cc` is not also blind-copied.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - to
              - body
              properties:
                to:
                  type: string
                  format: email
                subject:
                  type: string
                  minLength: 1
                  maxLength: 998
                  description: 'Required for new threads. Optional when threadId is set — the thread subject is used automatically (with Re: prefix).'
                body:
                  type: string
                  minLength: 1
                  maxLength: 100000
                  description: Email body. Plain text or HTML — HTML is auto-detected and rendered, with a plain-text fallback generated automatically.
                bodyHtml:
                  type: string
                  maxLength: 500000
                  description: Provide only when the plain-text and HTML versions must differ. Omit for normal sends — put HTML in `body` instead.
                threadId:
                  type: string
                  description: Thread ID for replies
                includeQuote:
                  type: boolean
                  description: When replying with threadId, append a quoted copy of the previous message to the body. Defaults to true. Set false to send only your reply text.
                replyTo:
                  type: string
                  format: email
                  description: 'Optional. Address that replies should be routed to (set as the `Reply-To` MIME header). Free plan: must be the address of an inbox you own in OpenMail. Pro plan or higher: any valid email address.'
                cc:
                  type: array
                  items:
                    type: string
                    format: email
                  description: Optional CC recipients. Visible to every recipient.
                unsubscribe:
                  type: boolean
                  description: 'Optional. Adds one-click `List-Unsubscribe` headers (RFC 8058), so Gmail, Yahoo and Apple Mail show a native unsubscribe button. Any `{{unsubscribe_url}}` in `body` or `bodyHtml` is replaced with the same link. A recipient who unsubscribes is suppressed for your whole organisation, and later sends to them, including replies, return 422 `recipient_suppressed` with type `unsub`. Can''t be combined with `cc` or `bcc`. A body containing `{{unsubscribe_url}}` without this flag is rejected.


                    On shared OpenMail domains this is automatic for any email to a single recipient (no `cc` or `bcc`) who is not another OpenMail inbox and has not written to you on the thread. It can''t be turned off there; use a custom domain for full control.'
                bcc:
                  type: array
                  items:
                    type: string
                    format: email
                  description: Optional BCC recipients. Delivered, but omitted from the message To and Cc recipients can read. Use this for CRM logging addresses (for example HubSpot or Salesforce) that only record the email when they are in BCC. Checked against the inbox's outbound recipient policy, same as `to` and `cc`.
                headers:
                  type: object
                  additionalProperties:
                    type: string
                  maxProperties: 20
                  description: 'Optional custom headers, as header name to string value, for example `{"List-Unsubscribe": "<https://example.com/u/123>"}` or `{"X-Campaign": "spring"}`. Up to 20 headers and 8 KB in total; each name plus value must fit in 996 characters. Values can''t contain line breaks. Headers that would change the sender, recipients, threading or authentication are rejected: From, Sender, To, Cc, Bcc, Subject, Date, Message-ID, In-Reply-To, References, Reply-To, Return-Path, MIME-Version, DKIM-Signature, Received, Authentication-Results, ARC-*, Feedback-ID, Delivered-To, X-Original-To, and anything starting with Content-, X-SES-, X-Mailgun- or X-OpenMail-. Use the matching field instead. Can''t set List-Unsubscribe when `unsubscribe` is true, or at all on a shared OpenMail domain.'
          multipart/form-data:
            schema:
              type: object
              required:
              - to
              - body
              properties:
                to:
                  type: string
                  format: email
                subject:
                  type: string
                  minLength: 1
                  maxLength: 998
                  description: 'Required for new threads. Optional when threadId is set — the thread subject is used automatically (with Re: prefix).'
                body:
                  type: string
                  minLength: 1
                  maxLength: 100000
                  description: Email body. Plain text or HTML — HTML is auto-detected and rendered, with a plain-text fallback generated automatically.
                bodyHtml:
                  type: string
                  maxLength: 500000
                  description: Provide only when the plain-text and HTML versions must differ. Omit for normal sends — put HTML in `body` instead.
                threadId:
                  type: string
                  description: Thread ID for replies
                includeQuote:
                  type: boolean
                  description: When replying with threadId, append a quoted copy of the previous message to the body. Defaults to true. Set false to send only your reply text.
                replyTo:
                  type: string
                  format: email
                  description: 'Optional. Address that replies should be routed to (set as the `Reply-To` MIME header). Free plan: must be the address of an inbox you own in OpenMail. Pro plan or higher: any valid email address.'
                cc:
                  type: array
                  items:
                    type: string
                    format: email
                  description: Optional CC recipients. Repeat the `cc` field once per address; a single field may also hold a comma-separated list or a JSON-encoded array of addresses.
                unsubscribe:
                  type: boolean
                  description: 'Optional. Adds one-click `List-Unsubscribe` headers (RFC 8058), so Gmail, Yahoo and Apple Mail show a native unsubscribe button. Any `{{unsubscribe_url}}` in `body` or `bodyHtml` is replaced with the same link. A recipient who unsubscribes is suppressed for your whole organisation, and later sends to them, including replies, return 422 `recipient_suppressed` with type `unsub`. Can''t be combined with `cc` or `bcc`. A body containing `{{unsubscribe_url}}` without this flag is rejected.


                    On shared OpenMail domains this is automatic for any email to a single recipient (no `cc` or `bcc`) who is not another OpenMail inbox and has not written to you on the thread. It can''t be turned off there; use a custom domain for full control.'
                bcc:
                  type: array
                  items:
                    type: string
                    format: email
                  description: Optional BCC recipients. Repeat the `bcc` field once per address; a single field may also hold a comma-separated list or a JSON-encoded array of addresses. Not visible to To or Cc recipients.
                headers:
                  type: string
                  description: Optional custom headers as a JSON-encoded object, for example `{"X-Campaign":"spring"}`. Same rules as the JSON `headers` field.
                attachments:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: File attachments (max 25 MB total per email)
      parameters:
      - schema:
          type: string
        in: path
        name: id
        required: true
      - schema:
          type: string
        in: header
        name: idempotency-key
        required: false
        description: 'Optional. Unique key (e.g. UUID) that makes retries safe: a repeat with the same key within 24 hours returns the original message instead of sending again. Generated server-side when omitted.'
      responses:
        '200':
          description: Email sent (or idempotent replay)
          content:
            application/json:
              schema:
                description: Email sent (or idempotent replay)
                type: object
                additionalProperties: true
                properties:
                  messageId:
                    type: string
                  threadId:
                    type: string
                  providerMessageId:
                    type: string
                  status:
                    type: string
                    enum:
                    - pending
                    - sent
                    - failed
        '400':
          description: Missing request body, invalid 'to'/'replyTo'/'cc'/'bcc' address, missing subject when required, `unsubscribe` combined with `cc`/`bcc`, or `{{unsubscribe_url}}` without `unsubscribe`, or invalid `headers` (`invalid_headers`)
          content:
            application/json:
              schema:
                description: Missing request body, invalid 'to'/'replyTo'/'cc'/'bcc' address, missing subject when required, `unsubscribe` combined with `cc`/`bcc`, or `{{unsubscribe_url}}` without `unsubscribe`, or invalid `headers` (`invalid_headers`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '402':
          description: Sending is paused because this account's monthly spend limit was reached (`spend_cap_paused`)
          content:
            application/json:
              schema:
                description: Sending is paused because this account's monthly spend limit was reached (`spend_cap_paused`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '403':
          description: A recipient is not permitted by the inbox's outbound correspondent policy (`recipient_not_permitted`, with the refused address in `recipient`). Also returned when a free-plan customer sets `replyTo` to an address that is not one of their own OpenMail inboxes (`feature_requires_paid_plan`), the account is banned (`account_banned`), or sending is suspended due to deliverability/abuse signals (`reputation_suspended`).
          content:
            application/json:
              schema:
                description: A recipient is not permitted by the inbox's outbound correspondent policy (`recipient_not_permitted`, with the refused address in `recipient`). Also returned when a free-plan customer sets `replyTo` to an address that is not one of their own OpenMail inboxes (`feature_requires_paid_plan`), the account is banned (`account_banned`), or sending is suspended due to deliverability/abuse signals (`reputation_suspended`).
                allOf:
                - $ref: '#/components/schemas/Error'
        '404':
          description: Inbox or thread not found
          content:
            application/json:
              schema:
                description: Inbox or thread not found
                allOf:
                - $ref: '#/components/schemas/Error'
        '409':
          description: The custom domain isn't ready to send yet (`domain_sending_pending`). Usually clears within minutes of verification; check the Retry-After response header.
          content:
            application/json:
              schema:
                description: The custom domain isn't ready to send yet (`domain_sending_pending`). Usually clears within minutes of verification; check the Retry-After response header.
                allOf:
                - $ref: '#/components/schemas/Error'
        '422':
          description: Content blocked (`unsafe_content`, `phishing_detected`), a recipient domain has no MX records (`no_mx_records`, with the failing domain in `domain`), recipient suppressed for bounce, complaint or unsubscribe (`recipient_suppressed`, with `type`), or account storage limit exceeded (`storage_limit_exceeded`)
          content:
            application/json:
              schema:
                description: Content blocked (`unsafe_content`, `phishing_detected`), a recipient domain has no MX records (`no_mx_records`, with the failing domain in `domain`), recipient suppressed for bounce, complaint or unsubscribe (`recipient_suppressed`, with `type`), or account storage limit exceeded (`storage_limit_exceeded`)
                allOf:
                - $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit exceeded (global daily, per-inbox daily, per-inbox burst) or cold outreach limit (`cold_outreach_limit`, with `scope`, `limit`, `resetsAt`, and a `nextStep` sentence on what raises the cap — see `GET /v1/limits/cold`). Check the Retry-After response header.
          content:
            application/json:
              schema:
                description: Rate limit exceeded (global daily, per-inbox daily, per-inbox burst) or cold outreach limit (`cold_outreach_limit`, with `scope`, `limit`, `resetsAt`, and a `nextStep` sentence on what raises the cap — see `GET /v1/limits/cold`). Check the Retry-After response header.
                allOf:
                - $ref: '#/components/schemas/Error'
        '503':
          description: '`unsubscribe` was requested but one-click unsubscribe is unavailable (`unsubscribe_unavailable`). Retry, or send without it.'
          content:
            application/json:
              schema:
                description: '`unsubscribe` was requested but one-click unsubscribe is unavailable (`unsubscribe_unavailable`). Retry, or send without it.'
                allOf:
                - $ref: '#/components/schemas/Error'
  /v1/inboxes/{id}/messages:
    get:
      operationId: listMessages
      summary: List messages
      tags:
      - Messages
      description: List all messages in an inbox, across every thread. Supports pagination for high-volume inboxes.
      parameters:
      - schema:
          type: integer
          default: 50
        in: query
        name: limit
        required: false
      - schema:
          type: integer
          default: 0
        in: query
        name: offset
        required: false
      - schema:
          $ref: '#/components/schemas/MessageDirection'
        in: query
        name: direction
        required: false
      - schema:
          type: string
        in: path
        name: id
        required: true
      responses:
        '200':
          description: List of messages
          content:
            application/json:
              schema:
                description: List of messages
                type: object
                additionalProperties: true
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Message'
                  total:
                    type: integer
        '401':
          description: Missing, invalid, or expired API key
          content:
            application/json:
              schema:
                description: Missing, invalid, or expired API key
                allOf:
                - $ref: '#/components/schemas/Error'
        '404':
          description: Inbox not found
          content:
            application/json:
              schema:
                description: Inbox not found
                allOf:
                - $ref: '#/components/schemas/Error'
  /v1/messages/{id}/raw:
    get:
      operationId: getRawMessage
      summary: Download raw message
      tags:
      - Messages
      description: Returns the original RFC 822 bytes of an inbound message as `message/rfc822`, exactly as received.
      parameters:
      - schema:
          type: string
        in: path
        name: id
        required: true
      responses:
        '200':
          description: Original message bytes
          content:
            message/rfc822:
              schema:
                type: string
                format: binary
              example: "Return-Path: <alice@example.com>\nReceived: from mail.example.com by inbound-smtp.eu-north-1.amazonaws.com\n for bot@yourdomain.com; Wed, 16 Sep 2026 18:22:58 +0000 (UTC)\nAuthentication-Results: amazonses.com; spf=pass; dkim=pass header.i=@example.com; dmarc=pass\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=sel;\n h=from:to:subject:date:message-id:content-type; bh=y/Rmyv...; b=ZoOqF3...\nFrom: Alice <alice@example.com>\nTo: bot@yourdomain.com\nSubject: Q3 report\nDate: Wed, 16 Sep 2026 14:22:53 -0400\nMessage-ID: <CAJ=a=Hx...@mail.example.com>\nMIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=\"b1\"\n\n--b1\nContent-Type: text/plain; charset=\"UTF-8\"\n\nReport attached.\n\n--b1\nContent-Type: application/pdf; name=\"report.pdf\"\nContent-Disposition: attachment; filename=\"report.pdf\"\nContent-Transfer-Encoding: base64\n\nJVBERi0xLjQKJ...\n--b1--\n"
        '401':
          description: Missing, invalid, or expired API key
          content:
            application/json:
              schema:
                description: Missing, invalid, or expired API key
                allOf:
                - $ref: '#/components/schemas/Error'
        '404':
          description: Message not found, or its original bytes weren't kept
          content:
            application/json:
              schema:
                description: Message not found, or its original bytes weren't kept
                allOf:
                - $ref: '#/components/schemas/Error'
  /v1/messages/{id}:
    delete:
      operationId: deleteMessage
      summary: Delete message
      tags:
      - Messages
      description: Delete a message from its inbox.
      parameters:
      - schema:
          type: string
        in: path
        name: id
        required: true
      responses:
        '204':
          description: Message deleted
        '401':
          description: Missing, invalid, or expired API key
          content:
            application/json:
              schema:
                description: Missing, invalid, or expired API key
                allOf:
                - $ref: '#/components/schemas/Error'
        '403':
          description: API key is inbox-scoped, or is not scoped to this message's inbox. Deleting mail needs an account-wide or pod-scoped key.
          content:
            application/json:
              schema:
                description: API key is inbox-scoped, or is not scoped to this message's inbox. Deleting mail needs an account-wide or pod-scoped key.
                allOf:
                - $ref: '#/components/schemas/Error'
        '404':
          description: Message not found
          content:
            application/json:
              schema:
                description: Message not found
                allOf:
                - $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      additionalProperties: true
      properties:
        error:
          type: string
        message:
          type: string
    Message:
      type: object
      additionalProperties: true
      properties:
        id:
          type: string
        threadId:
          type: string
        direction:
          $ref: '#/components/schemas/MessageDirection'
        fromAddr:
          type: string
        toAddr:
          type: string
          description: Your OpenMail inbox address for inbound messages. For outbound sends, the primary recipient.
        headerTo:
          type:
          - string
          - 'null'
          description: Inbound only. The address the sender wrote to (comma-separated if multiple). When deliveryRole is cc, reply to this address — not fromAddr.
        deliveryRole:
          type:
          - string
          - 'null'
          enum:
          - to
          - cc
          - null
          description: Inbound only. to if your inbox was the main recipient; cc if you were copied on the email.
        cc:
          type: array
          items:
            type: string
          description: Email addresses copied on the message.
        addressNames:
          type:
          - object
          - 'null'
          additionalProperties:
            type: string
          description: 'Inbound only. Display names the sender''s mail client put on From, To and Cc, keyed by lower-cased address, e.g. `{"alice@example.com": "Alice Smith"}`. Address fields stay bare so you can match on them. Null when the headers carried no names.'
        bcc:
          type: array
          items:
            type: string
          description: Blind-copy recipients on outbound mail sent from this inbox. Empty on inbound. Not included in the copy delivered to To or Cc.
        subject:
          type: string
        bodyText:
          type: string
        bodyHtml:
          type: string
        attachments:
          type: array
          items:
            type: object
            additionalProperties: true
            properties:
              filename:
                type: string
              contentType:
                type: string
              sizeBytes:
                type: integer
              url:
                type: string
                description: Signed URL for download
        rawUrl:
          type:
          - string
          - 'null'
          description: URL of the original RFC 822 message (GET /v1/messages/{id}/raw). null when the bytes weren't kept.
        status:
          type: string
          enum:
          - pending
          - sent
          - received
          - failed
        createdAt:
          type: string
          format: date-time
    MessageDirection:
      type: string
      enum:
      - inbound
      - outbound
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key