Launched on Launchit

Developer documentation

Mailient Agent API

Eight endpoints that let an external AI agent work with a Mailient user’s email: read what needs them, read one conversation, draft a reply in their voice, and send a draft they approved.

Mailient stays the security boundary. The agent never receives a Gmail access token, an OAuth refresh token, or any other credential belonging to the user or to Mailient’s integrations. Every Gmail operation happens inside Mailient, under the account the bearer token resolves to.

Base URL

https://mailient.xyz/api/v1

HTTPS only. Plain HTTP is redirected before it reaches the application, but a bearer token should never be sent over one — always use the https:// form. The machine-readable contract lives at https://mailient.xyz/api/v1/openapi.json and is public: a connector platform can read it without a credential.

Authentication

One static bearer token per integration. The account owner issues it at Settings → Agent API keys and chooses its scopes there. It is shown once and stored as a hash, so it cannot be recovered afterwards — only replaced.

Authorization: Bearer mak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
There is no user identifier in this API. No user_id parameter, no email field, no header that names an account. The token resolves to exactly one Mailient user, and that user is the only one the request can reach. A client-supplied identity is not an identity, so we do not accept one.

Scopes

ScopeGrants
inbox:readAll six read endpoints.
drafts:writePOST /v1/drafts. Saves to Gmail drafts. Cannot send.
email:sendPOST /v1/send. Off by default on every key — the account owner has to tick it explicitly when issuing the credential.

Getting started

One request proves the whole chain — credential, subscription, Gmail connection:

curl -s https://mailient.xyz/api/v1/me \
  -H "Authorization: Bearer $MAILIENT_KEY"

{
  "id": "acct_9f2c…",
  "email": "you@example.com",
  "name": "You",
  "connected_email": "you@example.com",
  "gmail_connected": true,
  "calendar_connected": true,
  "mailbox_status": "ok",
  "scopes": ["inbox:read", "drafts:write"]
}

gmail_connected is a live answer — Mailient actually reaches the mailbox to produce it, rather than reporting that a connection once existed. When it is false, mailbox_status tells you which problem it is: not_connected (the owner never connected one) or needs_reauth (they did, and it has stopped working). Either way the inbox endpoints will return 409 rather than an empty list — see Empty means empty.

Endpoints

MethodEndpointScopePurpose
GET/v1/meinbox:readThe Mailient account this key acts as, and whether Gmail is connected.
GET/v1/inbox/priorityinbox:readConversations that need the owner, each with a written reason.
GET/v1/inbox/summaryinbox:readCounts plus Mailient's one-line briefing for the day.
GET/v1/followupsinbox:readThreads the owner sent that nobody answered.
GET/v1/opportunitiesinbox:readCommercial signals in recent mail, with the phrase that triggered each.
GET/v1/threads/{thread_id}inbox:readOne conversation in full. Nothing else.
POST/v1/draftsdrafts:writeWrite a reply in the owner's voice and save it to Gmail.
POST/v1/sendemail:sendSend a draft this API created. Requires Idempotency-Key.

What the agent asks → what you call

"What emails need my attention?"
  GET /v1/inbox/priority?limit=5

"What follow-ups do I owe?"
  GET /v1/followups

"Anything commercially important come in?"
  GET /v1/opportunities

"Summarise my important email."
  GET /v1/inbox/summary        (the briefing + counts)
  GET /v1/inbox/priority       (the items behind them)

"Draft a response to the latest message."
  GET  /v1/threads/{thread_id}   (read what it actually says)
  POST /v1/drafts                { "thread_id": "...", "instruction": "..." }

"Send that reply."
  POST /v1/send                  { "draft_id": "..." }
  Idempotency-Key: <unique per intended send>

Reading what matters

curl -s "https://mailient.xyz/api/v1/inbox/priority?limit=3" \
  -H "Authorization: Bearer $MAILIENT_KEY"

{
  "items": [
    {
      "thread_id": "18f2a9c4d1e0b7a3",
      "message_id": "18f2a9c4d1e0b7a4",
      "subject": "Q3 proposal — revised numbers",
      "sender": "Priya Shah",
      "sender_email": "priya@acme.com",
      "reason": "Priya needs the Q3 budget approved before Friday's board call.",
      "evidence": ["4th email from her", "deadline is Friday"],
      "rank": 1,
      "priority": "high",
      "needs_action": true,
      "received_at": "2026-09-19T14:02:11.000Z",
      "gmail_url": "https://mail.google.com/mail/u/0/#all/18f2a9c4d1e0b7a3"
    }
  ],
  "count": 1,
  "generated_at": "2026-09-20T08:14:03.221Z",
  "cached": true,
  "message": "1 conversation needs attention, most important first."
}

reasonis written by Mailient’s triage model from the message itself — it is the field that lets an agent say why something matters rather than reading out subject lines. rank is the model’s ordering; priorityis a band derived from it (1–3 high, 4–6 medium, 7+ low), not a separate score.

Drafting and sending

curl -s -X POST https://mailient.xyz/api/v1/drafts \
  -H "Authorization: Bearer $MAILIENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"thread_id":"18f2a9c4d1e0b7a3","instruction":"Confirm Tuesday works and ask for the deck."}'

{
  "draft_id": "r-482910385512",
  "thread_id": "18f2a9c4d1e0b7a3",
  "to": "priya@acme.com",
  "subject": "Re: Q3 proposal — revised numbers",
  "body": "Hi Priya,\n\nTuesday works…",
  "status": "draft",
  "voice_match_score": 86,
  "gmail_url": "https://mail.google.com/mail/u/0/#drafts/…",
  "message": "Draft saved to Gmail. Nothing has been sent."
}

curl -s -X POST https://mailient.xyz/api/v1/send \
  -H "Authorization: Bearer $MAILIENT_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4d0f1c7a-1d1e-4e37-9d1a-0b1a2c3d4e5f" \
  -d '{"draft_id":"r-482910385512"}'

{ "status": "sent", "message_id": "…", "thread_id": "…",
  "to": "priya@acme.com", "subject": "Re: Q3 proposal — revised numbers",
  "idempotent_replay": false, "message": "Sent to priya@acme.com." }

Permissions and approval

Sending is the only irreversible thing this API does, so it is constrained four ways at once:

On top of that, a connector platform normally runs its own approval prompt before a consequential action. That is welcome and expected — but this API does not assume it happened. The four constraints above hold regardless of what the client did.

Idempotency

Send any unique string up to 200 characters as Idempotency-Key, one per intended send. Reuse it for retries of that same send.

Keys are scoped to your credential and retained for 24 hours.

Errors

Every non-2xx response has the same shape. Branch on error.code, never on error.message — the codes are part of the contract, the prose is not.

{
  "error": {
    "code": "THREAD_NOT_FOUND",
    "message": "The requested thread could not be found.",
    "request_id": "req_6f1c…"
  }
}
CodeHTTPMeaning
MISSING_CREDENTIAL401No Authorization header.
INVALID_CREDENTIAL401The key is not recognised.
CREDENTIAL_EXPIRED401The key passed its expiry.
CREDENTIAL_REVOKED401The owner revoked it. Ask them for a new one.
SUBSCRIPTION_REQUIRED402The Mailient account has no active subscription.
INSUFFICIENT_SCOPE403The key lacks the scope this operation needs. `required_scope` says which.
THREAD_NOT_FOUND404No such thread in this account.
DRAFT_NOT_FOUND404No such draft in this account.
INVALID_REQUEST400Validation failed. The message names the field.
IDEMPOTENCY_KEY_REQUIRED400POST /v1/send needs an Idempotency-Key header.
GMAIL_NOT_CONNECTED409The owner has not connected a mailbox.
GMAIL_REAUTH_REQUIRED409The mailbox connection needs re-authorising in Mailient.
REQUEST_IN_PROGRESS409An identical send is still in flight. Retry shortly.
IDEMPOTENCY_KEY_REUSED422That key was already used for a different body.
RATE_LIMITED429See `retry_after_seconds`.
INTERNAL_ERROR500Our fault. Quote `request_id`.
UPSTREAM_GMAIL_ERROR502Gmail refused or failed the operation.
DRAFT_GENERATION_FAILED502The writer could not produce a grounded reply. Nothing was saved.
AI_UNAVAILABLE503Mailient's writing service is down. Nothing was saved.
CREDENTIAL_STORE_UNAVAILABLE503Credentials cannot be verified right now. Retry.

Rate limits

These exist to stop a retry loop turning into thousands of Gmail calls. All but the first are counted per account, not per key, so issuing a second credential does not buy a second allowance.

Every request, per credential120 / minute
/v1/inbox/* and /v1/followups60 / hour
/v1/opportunities20 / hour
/v1/threads/{id}200 / hour
/v1/drafts40 / hour
/v1/send20 / hour

Over a limit returns 429 with retry_after_seconds. The inbox endpoints are backed by a five-minute snapshot cache, so polling them is cheap — the response tells you whether you got a cached snapshot via cached.

Empty means empty

An agent that reports “nothing needs you today” when the truth is “we could not look” is worse than one that fails. So:

Security model

agent (Muse, or any HTTP client)
  │  Authorization: Bearer mak_live_…
  ▼
Mailient Agent API  ── scope · subscription · rate limit · request log
  ▼
Mailient's own auth + integration layer
  ▼
Gmail  (via the user's existing Mailient connection)

Versioning

The path carries the version. Within /v1 we may add fields and endpoints; we will not remove a field, change a type, or repurpose an error code. Treat unknown fields as additive and ignore them.

Something wrong, or something missing? Quote the request_id from the response and get in touch.