mails-ai Public API

Unauthenticated endpoints (health, unsubscribe link targets).

Operations 3

GET /v1/unsubscribe Unsubscribe landing page #
POST /v1/unsubscribe One-click unsubscribe (RFC 8058) #
GET /api/health Health check #

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/mails-ai:mails-ai-public-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

mails-ai-public-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Mails.ai Public API
  version: 0.2.0
  description: 'The Mails.ai API gives AI agents a real email identity: send and receive

    transactional email. Mail received within your plan''s limits is scanned for prompt injection and

    reaches you as a typed event. With classification turned on (a paid-plan feature), new mail can also

    be read for intent; a reply in a thread never is.


    ## Authentication

    All endpoints take a Bearer API key:


    ```

    Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    ```


    Keys carry one or more scopes — `send`, `read`, `draft` (writes drafts, but can neither send nor

    schedule one) and `manage` — and a mode: **live** (`mk_live_…`) or **test** (`mk_test_…`).


    ## Test mode

    A `mk_test_` key exercises the entire API — real validation, persisted resources, real

    `message.sent`/`*.received` events and webhooks — without sending real email or incurring cost. Build and

    test your whole integration on a test key, then flip one character to `mk_live_` to go live.

    `POST /v1/test/inbound` (test key only) lets you simulate inbound mail end-to-end.


    ## Idempotency

    Send endpoints accept an `Idempotency-Key` header; a repeated key replays the stored response instead of

    sending twice, with an `Idempotent-Replay: true` header.


    ## Pagination

    List endpoints are cursor-paginated: pass `limit` (1–100) and the `next_cursor` from the previous page.


    ## Errors

    Errors use a stable envelope: `{ "error": { "type", "code", "message", "param", "request_id" } }`. Every

    response carries an `X-Request-Id` header for support correlation.'
  contact:
    name: Mails.ai Support
    url: https://mails.ai
    email: support@mails.ai
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
- url: https://api.mails.ai
  description: Production
security:
- bearerAuth: []
tags:
- name: Public
  description: Unauthenticated endpoints (health, unsubscribe link targets).
paths:
  /v1/unsubscribe:
    get:
      tags:
      - Public
      summary: Unsubscribe landing page
      operationId: unsubscribe
      description: Public List-Unsubscribe link target (human click). Authorized by the signed `t` token, not an API key. Always returns an HTML confirmation page (200) regardless of token validity.
      parameters:
      - name: t
        in: query
        required: false
        schema:
          type: string
        description: Signed unsubscribe token embedded in the email.
      responses:
        '200':
          description: An HTML confirmation page.
          content:
            text/html:
              schema:
                type: string
      security: []
    post:
      tags:
      - Public
      summary: One-click unsubscribe (RFC 8058)
      operationId: unsubscribeOneClick
      description: RFC 8058 one-click unsubscribe POST from a mail client. Authorized by the signed `t` query token. Returns an empty 200.
      parameters:
      - name: t
        in: query
        required: false
        schema:
          type: string
        description: Signed unsubscribe token.
      responses:
        '200':
          description: Processed (empty body).
      security: []
  /api/health:
    get:
      tags:
      - Public
      summary: Health check
      operationId: getHealth
      description: Liveness probe. Without authentication it returns `status` (`healthy` when the database answers and accepts writes, else `degraded`) and `timestamp`. The dependency detail (database, sender, classifier) is returned only to internal callers.
      responses:
        '200':
          description: Service health.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'
      security: []
components:
  schemas:
    Health:
      type: object
      properties:
        status:
          type: string
          enum:
          - healthy
          - degraded
          description: '`healthy` when the database answers and accepts writes, `degraded` otherwise. The HTTP status is 200 either way.'
        version:
          type: string
          description: The API's version string. Like `db`, `ses`, `classifier` and `schemas`, it is returned to internal callers only.
        db:
          type: object
          properties:
            status:
              type: string
              enum:
              - ok
              - error
              description: '`ok`, or `error` when the database did not answer or is read-only.'
            error:
              type: string
              description: What went wrong, when `status` is `error`.
          required:
          - status
          description: The database check.
        ses:
          type: object
          properties:
            mocked:
              type: boolean
              description: True when the SES adapter is mocked or has no credentials, so it sends no real mail.
            region:
              type: string
              description: The AWS region the SES adapter sends from.
          required:
          - mocked
          - region
          description: The Amazon SES adapter, which matters only when SES is the mail provider in use.
        classifier:
          type: object
          properties:
            mocked:
              type: boolean
              description: True when the classifier returns fixed test verdicts instead of calling its model.
            model:
              type: string
              description: The model the classifier calls when it is not mocked.
          required:
          - mocked
          - model
          description: The inbound classifier.
        schemas:
          type: integer
          description: How many definitions the database schema exports.
        timestamp:
          type: string
          format: date-time
          description: When the check ran (UTC).
      required:
      - status
      - timestamp
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: mk_live_<32 hex>, mk_test_<32 hex>, or moa_<48 hex>
      description: 'A Mails.ai API key, or an OAuth access token (`moa_…`, valid for 1 hour) from signing in through an app such as the CLI, Claude or ChatGPT. OAuth discovery: https://api.mails.ai/.well-known/oauth-authorization-server and https://api.mails.ai/.well-known/oauth-protected-resource. An access token''s OAuth scope maps onto these API scopes: `full_access` = `send` + `read` + `manage` (not `draft`: the draft routes take those three too), `send` = `send` + `read`, `read` = `read`. Scopes (`send`/`read`/`manage`/`draft`) are noted per operation via `x-required-scope`; when more than one scope opens an operation (the draft routes also take `draft`), `x-accepted-scopes` lists them all. `inbox` appears there too, on the triage routes (changing and trashing threads, labels, cancelling a scheduled send): only a signed-in dashboard session holds it (a workspace Member''s), never an API key or OAuth token, and `x-session-scopes` names it.'