The mails CLI
Every command below comes from the CLI’s own command table (mails commands --json, version 0.1.0), so this page and mails --help list the same commands and flags. The examples act on hello, the agent a new workspace starts with; the ones that create an agent, archive one or test a forward act on a second agent, which needs a paid plan.
Installation
The CLI needs Node.js 20 or newer and installs no other packages.
npm install -g @mailsai/clinpx @mailsai/cli --helpThen run mails doctor: it checks Node, your sign-in, the connection, your clock and what your key is allowed to do.
Authentication
The CLI picks its credential in this order: --api-key, then MAILS_API_KEY, then a saved profile (--profile, MAILS_PROFILE, or the active one). On a server or in CI, use an API key in MAILS_API_KEY.
# Sign in through your browser, saved as the profile "default"
mails login
# A second workspace: choose it in the browser
mails login --profile acme
# Paste an API key instead; it is never echoed
mails login --api-key
# Which key, workspace and scopes are in use
mails whoamiThe browser sign-in can do no more than your role in the workspace allows: as an owner or admin it can do everything, as a member it can send and read but cannot create API keys, agents or domains, and as a billing member it can only read.
Every command in this section works without signing in, except mails whoami and mails workspaces current.
mails login
Sign in through your browser, or with an API key.
Opens your browser to approve the CLI, then saves the sign-in as a profile (in the OS keychain when there is one, otherwise in a file only you can read). With --api-key it saves a key instead: paste it at the prompt, or pipe it in. Nothing is ever echoed, and only the first 12 characters of a key are ever shown.
| Flag | Description |
|---|---|
--scope <a,b,…> | What the browser sign-in asks for (default full_access). It gets no more than your role in the workspace allows. One of: full_access, send, read. |
--no-browser | Print the sign-in address instead of opening a browser. Open it in a browser on this same computer, because the sign-in comes back to 127.0.0.1; on a remote machine, use --api-key. |
--timeout <n> | How long to wait for the browser (default 300). |
--store <value> | Where to keep the credential (default auto, or MAILS_CREDENTIAL_STORE). One of: auto, keychain, file. |
mails login
mails login --api-key
mails login --api-key < key.txt
mails login --profile staging --api-url https://staging.example.commails logout
Sign out: forget a saved profile (and end its browser sign-in).
| Flag | Description |
|---|---|
--all | Sign out of every profile. |
mails logout
mails logout --profile staging
mails logout --allProfiles
| Command | Description |
|---|---|
mails whoami | Show which key, workspace and agent the CLI is using. |
mails auth list | List saved profiles. Also: mails auth ls. |
mails auth switch <profile> | Make another saved profile the active one. <profile>: Profile name (see mails auth list). Also: mails auth use. |
mails auth rename <profile> <new-name> | Give a saved profile a new name. <profile>: The profile's current name. <new-name>: Its new name. Also: mails auth mv. |
mails auth remove <profile> | Forget a saved profile. <profile>: Profile name (see mails auth list). Also: mails auth rm. |
mails whoami
mails whoami --json | jq -r .workspace.slug
mails auth list
mails auth list --json
mails auth switch staging
mails auth rename default work
mails auth remove stagingWorkspaces
Every key and every browser sign-in belongs to one workspace. To add another workspace, run mails login --profile <name> and choose it in the browser.
| Command | Description |
|---|---|
mails workspaces list | List the workspaces you have signed in to. Also: mails workspaces ls. |
mails workspaces current | Show the workspace the CLI is using. |
mails workspaces switch <workspace> | Use another workspace you have signed in to. <workspace>: Workspace slug, id or profile name. Also: mails workspaces use. |
mails workspaces list
mails workspaces current
mails workspaces switch acmeScripts and AI agents
People get tables and everything else gets JSON. When stdout is not a terminal (a pipe, a file, an agent), a command prints JSON only, except mails emails raw, which prints text, mails receiving attachment, which writes the file itself, mails completion, which prints a shell script, and --help, which is always text. --json forces JSON in a terminal, and --no-json keeps the table in a pipe.
Errors are JSON on stderr, with exit code 1:
{ "error": { "code": "invalid_api_key", "message": "API key not recognized.", "request_id": "req_…", "status": 401, "hint": "…" } }mails commands --jsonlists every command with its arguments, flags, types, allowed values, examples, the API route behind it and the key scope it needs.- Bodies can come from files or stdin:
--text-file,--html-file,--body-file, or--data -to pipe a JSON body in. Flags given alongside override its fields. - Sends carry an idempotency key, so a retry after a dropped connection never sends twice. Network failures, 429s and 5xx answers on reads are retried with backoff.
--dry-runprints the request a command would make, without the credential and with file contents shortened, instead of sending it. Only the commands that call one API route do this; the others, such asmails login,mails logoutormails events tail, ignore it and run for real.--allfollows pagination to the end.
mails receiving list --json | jq -r '.data[0].subject'
# Wait for one inbound email, then exit
mails events tail --type message.received --count 1 --json
cat batch.json | mails emails batch --data -Emails
Send email and look up what was sent. Also available as mails messages.
mails emails list
List sent emails. Also: mails emails ls, mails messages list.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--agent-id <value> | Filter by agent id or name. A key tied to one agent always reads that agent's mail, and naming another agent is a 403. Also: --agent. |
--thread-id <value> | Filter by thread id. |
--q <value> | Case-insensitive substring of the subject, body or recipient addresses. For word search across all mail, use GET /v1/search. |
--all | Fetch every page, not just the first. |
mails emails list --limit 10
mails emails list --agent-id hello --all --jsonmails emails send
Send an email. Also: mails messages send.
Sends an email from one of your agents. With a mk_test_ key the message is stored and its message.sent event reaches your webhooks (marked test_mode: true), but no email is sent and nothing is billed. Quota, suppression and rate-limit refusals are skipped, and a message the cold-email firewall would refuse comes back 201 with classifier_warning instead.
| Flag | Description |
|---|---|
--idempotency-key <value> | Replays the stored response for a repeated key (kept 24 hours), so a retried request never double-sends; the replay carries Idempotent-Replay: true. |
--agent <value> | Which agent sends this — its name or agt_ id. |
--from <value> | The sender you want, as a handle or address — billing, billing@acme.com and Acme <billing@acme.com> all mean the agent called billing. |
--to <a,b,…> | Required. Who the message goes to: one address, or a list of 1 to 50. |
--cc <a,b,…> | Addresses to copy, up to 50. Every recipient sees them. |
--bcc <a,b,…> | Addresses to blind-copy, up to 50. They receive the message but appear in none of its headers. |
--subject <value> | Required. Subject line, 1 to 998 characters. |
--text <value> | Plain-text body, up to 1,000,000 characters. At least one of body_text and body_html is required, or their aliases text and html. Also: --body-text. |
--text-file <value> | Read the plain-text body from a file (- for stdin). |
--html <value> | HTML body, up to 1,000,000 characters. With body_text as well, the email carries a plain-text part and an HTML part. Also: --body-html. |
--html-file <value> | Read the HTML body from a file (- for stdin). |
--reply-to <value> | The address replies should go to. Left out, replies go to the agent's receiving_address. |
--in-reply-to-message-id <value> | Id of the message this one answers (msg_… or rcv_…), named in the email's In-Reply-To header. The send joins its thread when the same agent sent or received it. |
--references <a,b,…> | Message-IDs for the References header. Write one from another system with its angle brackets (<…>); a value without them is read as a mails.ai message id. |
--attach <file> | A local file (about 3 MB in all, sent inside the request) or an https URL that mails.ai fetches (25 MB in all). Repeat for several. Not with a future --scheduled-at. Up to 10. Also: --attachment, --attachments. |
--inline <file> | A file to show inside the HTML, as id=file or id=https-url: <img src="cid:id"> in --html shows it. Repeat the flag for several. |
--metadata <key=value> | Your own key-value strings, kept with the message and returned by GET /v1/messages/{id}; they are not part of the email. Keys up to 64 characters, values up to 500. |
--scheduled-at <value> | Send at this time (ISO 8601) instead of now. A time in the past sends at once. A future time cannot be combined with attachments. |
--tag <name=value> | Up to 10 name-value labels, kept with the message and included in its message.sent event; they are not part of the email. Also: --tags. |
--list-unsubscribe | Add one-click unsubscribe (RFC 8058), for newsletters and digests. One click suppresses the address workspace-wide. Only for exactly one recipient, with no Cc or Bcc. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails emails send --to jordan@example.com --subject "Your order shipped" --text "It is on its way."
mails emails send --from hello --to a@example.com,b@example.com --subject "Hello" --html-file ./hello.html --attach ./invoice.pdf
cat message.json | mails emails send --data -mails emails batch
Send up to 100 emails in one request. Also: mails messages batch.
Sends up to 100 messages in one request. Per-message failures are returned inline in data[] and do not fail the batch; the HTTP status is still 201. Each failure is the error a single send would answer: a message a send limit refused carries resets_at and retry_after_seconds, one the email service would not take for now (503 upstream_unavailable) retry_after_seconds, and a plan refusal upgrade_url.
The body is a JSON array of up to 100 messages, each with the fields of mails emails send except attachments: an item with attachments refuses the whole batch with 422.
| Flag | Description |
|---|---|
--idempotency-key <value> | Replays the stored response for a repeated key (kept 24 hours), so a retried request never double-sends; the replay carries Idempotent-Replay: true. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails emails batch --body-file ./batch.json
cat batch.json | mails emails batch --data -mails emails appeal
Ask for a second review of a send refused as cold outreach. Also: mails messages appeal.
Requests a second, independent review of content refused with cold_email_prohibited. Submit the SAME subject and body the refused send used.
| Flag | Description |
|---|---|
--agent <value> | Agent name or id the refused send used. Optional — omitted, the workspace's single agent is assumed (the refused send itself created one if none existed). |
--subject <value> | The refused message's subject, verbatim. |
--text <value> | The refused message's body_text, verbatim. Also: --body-text. |
--text-file <value> | Read the plain-text body from a file (- for stdin). |
--html <value> | The refused message's body_html, verbatim. Also: --body-html. |
--html-file <value> | Read the HTML body from a file (- for stdin). |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails emails appeal --subject "Your invoice" --body-text "Hi Jordan, your invoice is attached."mails emails get
Show one sent email. Also: mails messages get.
| Argument | Description |
|---|---|
<id> | Required. Sent message id (msg_…). |
mails emails get msg_01JABCmails emails reschedule
Change when a scheduled email goes out. Also: mails emails update, mails messages reschedule.
Moves a scheduled send to a new future scheduled_at, and raises message.scheduled again with the new scheduled_at and the previous_scheduled_at it replaced. It needs the send scope as well as manage, and a live key for a live message (403 live_mode_required); the key that scheduled the send keeps answering for it.
| Argument | Description |
|---|---|
<id> | Required. Sent message id with status scheduled, or rejected because its agent was paused or archived. |
| Flag | Description |
|---|---|
--scheduled-at <value> | The new send time: an ISO 8601 UTC time ending in Z, which must be in the future (400 invalid_field otherwise). Left out, the schedule stays as it is. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails emails reschedule msg_01JABC --scheduled-at 2026-12-01T09:00:00Zmails emails reply
Reply in the same thread to a sent or received email. Also: mails messages reply.
Replies in-thread to a sent or received message. Sets In-Reply-To, References, and a Re: subject server-side. A reply cannot carry attachments (422 invalid_field, param attachments): to reply with files, send with POST /v1/messages and in_reply_to_message_id.
| Argument | Description |
|---|---|
<id> | Required. The message being replied to (msg_… or rcv_…). |
| Flag | Description |
|---|---|
--idempotency-key <value> | Replays the stored response for a repeated key (kept 24 hours), so a retried request never double-sends; the replay carries Idempotent-Replay: true. |
--text <value> | Plain-text body of the reply, up to 1,000,000 characters, sent as written: the original is not quoted. Also: --body-text. |
--text-file <value> | Read the plain-text body from a file (- for stdin). |
--html <value> | HTML body of the reply, up to 1,000,000 characters, sent as written. With body_text as well, the email carries both parts. Also: --body-html. |
--html-file <value> | Read the HTML body from a file (- for stdin). |
--reply-all | Also copy the original's other recipients: on a received message its To and Cc addresses, on one of your sent messages its Cc. Default false. |
--cc <a,b,…> | Addresses to copy, up to 50. With reply_all, the original's other recipients join these. |
--bcc <a,b,…> | Addresses to blind-copy, up to 50. They receive the reply but appear in none of its headers. |
--tag <name=value> | Up to 10 name-value labels, kept with the reply and included in its message.sent event; they are not part of the email. Also: --tags. |
--scheduled-at <value> | Send the reply at this time instead of now: an ISO 8601 UTC time ending in Z. A time in the past sends at once. |
--list-unsubscribe | Add one-click unsubscribe (RFC 8058), for newsletters and digests. One click suppresses the address workspace-wide. Only for exactly one recipient, with no Cc or Bcc. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails emails reply rcv_01JABC --text "Thanks, that works for us."
mails emails reply rcv_01JABC --text-file ./reply.txt --reply-allmails emails forward
Forward a sent or received email to new recipients. Also: mails messages forward.
Forwards a sent or received message to new recipients with a quoted body and Fwd: subject. Always sends immediately. Both parts carry the forwarded message: the HTML part is the covering note (body_html, else body_text), the forwarded header and the original (its own HTML, or its text), sent whenever there is HTML to carry.
| Argument | Description |
|---|---|
<id> | Required. The message being forwarded (msg_… or rcv_…). |
| Flag | Description |
|---|---|
--idempotency-key <value> | Replays the stored response for a repeated key (kept 24 hours), so a retried request never double-sends; the replay carries Idempotent-Replay: true. |
--to <a,b,…> | Required. Who the forward goes to: one address, or a list of 1 to 50. |
--cc <a,b,…> | Addresses to copy, up to 50. Every recipient sees them. |
--bcc <a,b,…> | Addresses to blind-copy, up to 50. They receive the forward but appear in none of its headers. |
--subject <value> | Subject of the forward, 1 to 998 characters. Left out, it is Fwd: and the original's subject, kept as it is when that already starts with Fwd: or Fw:. |
--text <value> | Your note, up to 1,000,000 characters, placed above a ---------- Forwarded message ---------- block that quotes the original's From, To, Subject and plain text. Also: --body-text. |
--text-file <value> | Read the plain-text body from a file (- for stdin). |
--html <value> | Your note as HTML, up to 1,000,000 characters, placed above the same ---------- Forwarded message ---------- block as the plain-text part and then the original's own HTML (or its plain text when it has none). Also: --body-html. |
--html-file <value> | Read the HTML body from a file (- for stdin). |
--list-unsubscribe | Add one-click unsubscribe (RFC 8058), for newsletters and digests. One click suppresses the address workspace-wide. Only for exactly one recipient, with no Cc or Bcc. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails emails forward rcv_01JABC --to warehouse@example.com --text "For your records."mails emails cancel
Cancel a scheduled email before it goes out. Also: mails messages cancel.
Cancels a send that is still scheduled, so it is never sent. Once the send starts going out it can no longer be canceled: the cancel changes nothing and answers 409 duplicate_resource, also when the send started while the cancel was running. A test key cannot cancel a send a live key scheduled (403 live_mode_required).
| Argument | Description |
|---|---|
<id> | Required. Sent message id with status scheduled. |
mails emails cancel msg_01JABCmails emails raw
Download an email as a raw .eml file. Also: mails messages raw.
Returns the message as a downloadable .eml attachment: the exact bytes as received when the raw message was kept (received mail up to 25 MiB), otherwise an RFC822 message rebuilt from the stored fields, with the Message-ID, From, Reply-To, In-Reply-To and References it went out or came in with.
| Argument | Description |
|---|---|
<id> | Required. Sent (msg_…) or received (rcv_…) message id. |
| Flag | Description |
|---|---|
-o, --output <value> | Write the result to this file instead of stdout. |
mails emails raw msg_01JABC --output message.eml
mails emails raw msg_01JABC | lessReceiving
Read received email, download attachments, listen for new mail.
mails receiving list
List received emails. Also: mails receiving ls.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--agent-id <value> | Filter by agent id or name. A key tied to one agent always reads that agent's mail, and naming another agent is a 403. Also: --agent. |
--thread-id <value> | Filter by thread id. |
--q <value> | Case-insensitive substring of the subject, body or sender address. For word search across all mail, use GET /v1/search. |
--all | Fetch every page, not just the first. |
mails receiving list
mails receiving list --agent-id hello --json | jq '.data[0].id'mails receiving get
Show one received email.
| Argument | Description |
|---|---|
<id> | Required. Received message id (rcv_…). |
mails receiving get rcv_01JABC
mails receiving get rcv_01JABC --json | jq -r .extracted_textmails receiving attachments
List the attachments of a received email.
Every file in the message, embedded images included (inline: true). Files that could not be kept are listed under omitted.
| Argument | Description |
|---|---|
<id> | Required. Received message id (rcv_…). |
mails receiving attachments rcv_01JABCmails receiving attachment
Download one attachment of a received email.
The file's bytes, always as a download (Content-Disposition: attachment) with X-Content-Type-Options: nosniff. Content-Type is the declared type when it is on a safe list, else application/octet-stream; HTML and SVG are never served as themselves. ?inline=1 asks for inline display, granted only for PNG, JPEG, GIF, WebP, AVIF and BMP images.
| Argument | Description |
|---|---|
<id> | Required. Received message id (rcv_…). |
<attachment-id> | Required. Attachment id (att_…). |
| Flag | Description |
|---|---|
-o, --output <value> | Write the result to this file instead of stdout. |
mails receiving attachment rcv_01JABC att_01JABC --output invoice.pdfmails receiving simulate
Deliver a made-up inbound email to an agent (test key only).
Drives the receive/classify half of the product with a hand-supplied envelope: runs the real classifier, threads the message, persists a received row, and emits the same typed event + webhook a real inbound would. Set in_reply_to_message_id to a prior test-send id to simulate a reply.received.
| Flag | Description |
|---|---|
--agent <value> | Required. The agent that receives the message: its name or agt_ id. |
--from <value> | Sender address. Required unless raw_base64 is sent, in which case it defaults to the raw email's From. |
--from-name <value> | Sender display name, up to 256 characters. Left out, it is the raw email's From name when raw_base64 is sent; otherwise the message has none. |
--subject <value> | Subject line, up to 2,000 characters. Left out, it is the raw email's Subject when raw_base64 is sent; otherwise the message has none. |
--text <value> | Message text. Required unless raw_base64 is sent, in which case it defaults to the raw email's text. Also: --body-text. |
--text-file <value> | Read the plain-text body from a file (- for stdin). |
--raw-base64 <value> | A complete raw email (RFC 822), base64-encoded, up to 3 MB decoded. |
--in-reply-to-message-id <value> | Id (msg_…) of one of this agent's sent messages, to simulate a reply to it. |
--spf <value> | Pretend SPF result, taken as given (default pass). One of: pass, fail, unknown. |
--dkim <value> | Pretend DKIM result, taken as given (default pass). With spf, both fail simulates message.received.unauthenticated. One of: pass, fail, unknown. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails receiving simulate --agent hello --from jordan@example.com --subject "Where is my order?" --body-text "Order 10473 has not arrived."mails receiving listen
Print new mail as it arrives. Also: mails receiving tail, mails receiving watch.
Waits for inbound email and prints who it is from, the subject and the start of the body (one JSON object per line when piped or with --json; add --full for the whole message). Mail from a forged sender (no DKIM signature aligned with the From domain verifies, and that domain's DMARC policy is quarantine or reject) is left out unless you add --include-unauthenticated. Stop it with Ctrl-C, --count or --timeout.
| Flag | Description |
|---|---|
--agent <value> | Only mail for this agent. Also: --agent-id. |
--full | Fetch and print the whole message, not just the event. |
--include-unauthenticated | Also show mail from a forged sender (message.received.unauthenticated). |
--since <value> | Start after this event id, or from the position a stopped run printed, instead of from now. |
--from-start | Replay the workspace's whole event history first. |
--position-file <value> | Start where the last run that used this file stopped, and keep the place saved in it: runs one after another miss no event and repeat none. |
-n, --count <n> | Stop after this many events. |
--timeout <n> | Stop after this long (exit 0). |
mails receiving listen
mails receiving listen --agent hello --json
mails receiving listen --count 1 --json | jq -r ".data.subject"Agents
The addresses that send and receive.
mails agents list
List agents. Also: mails agents ls.
Lists the workspace's agents, newest first. A key tied to one agent lists that agent and no other.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--status <value> | Which agents to list. One of: live, all, active, paused, archived. Default: live. |
--all | Fetch every page, not just the first. |
mails agents list
mails agents list --status all --jsonmails agents create
Create an agent: an address that sends and receives.
Creates an agent (a send/receive identity) at name@<workspace>.mails.ai, or at an address on a domain your workspace has verified for sending. Add forwarding: true when the mail service that already hosts that address will forward its mail to the agent's forwarding_address (no MX change), then test it with POST /v1/agents/{id}/forwarding/verify.
A new workspace starts with one agent, hello. The Free plan allows one agent, so another needs a paid plan: mails billing portal prints the link to upgrade.
| Argument | Description |
|---|---|
[name] | The agent's name and the part of its address before the @: 1 to 32 lowercase letters, digits, ., _ or -. |
| Flag | Description |
|---|---|
--domain <value> | The domain of the agent's address. Left out, it is your workspace's <slug>.mails.ai, or the domain of address when that is given. |
--address <value> | The agent's address, on a domain your workspace has verified for sending (support@acme.com). The agent is named after its local part, so name may be left out. |
--forwarding | Forwarding mode, for an address on your own domain: the mail service hosting it forwards its mail to the agent's forwarding_address, so no MX record changes. |
--allowlist-domains <a,b,…> | When set, this agent sends only to these domains and their subdomains (acme.com covers mail.acme.com). Up to 100. |
--blocklist-domains <a,b,…> | This agent never sends to these domains or their subdomains. Up to 100. |
--daily-send-limit <n> | This agent's own cap on recipients in any 24 hours, on top of your plan's cap: each live send counts once per to, cc and bcc address. |
--hourly-send-limit <n> | This agent's own cap on recipients in any hour, on top of your plan's cap: each live send counts once per to, cc and bcc address. |
--classify-inbound | Also read this agent's new mail for intent, entities and urgency (never a reply in a thread). Needs a paid plan (402 feature_not_enabled). Off unless set. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails agents create support
mails agents create billing --domain mail.acme.com --daily-send-limit 5000
mails agents create --address support@acme.com --forwardingmails agents get
Show one agent.
Returns an agent by its id or its name. A key tied to one agent gets any other agent as 404 agent_not_found, as if it did not exist, and a name other agents share is not reported to it as a 409.
| Argument | Description |
|---|---|
<id> | Required. Agent id or name. |
mails agents get hellomails agents update
Change an agent's settings, or pause it.
Changes an agent's status, recipient lists, send limits, or whether its new mail is read for intent. A pause by our abuse-prevention system or for the agent's own bounce or complaint rate cannot be lifted here (403 agent_suspended), and pausing such an agent again keeps that reason.
| Argument | Description |
|---|---|
<id> | Required. Agent id or name. |
| Flag | Description |
|---|---|
--status <value> | paused makes the agent's send requests fail with 422 agent_paused; active resumes it. One of: active, paused. |
--allowlist-domains <a,b,…> | When set, this agent sends only to these domains and their subdomains (acme.com covers mail.acme.com). Up to 100. |
--blocklist-domains <a,b,…> | This agent never sends to these domains or their subdomains. Up to 100. |
--daily-send-limit <n> | This agent's own cap on recipients in any 24 hours, on top of your plan's cap: each live send counts once per to, cc and bcc address. |
--hourly-send-limit <n> | This agent's own cap on recipients in any hour, on top of your plan's cap: each live send counts once per to, cc and bcc address. |
--classify-inbound | Turns intent classification of new mail on or off (never replies in a thread). Turning it on needs a paid plan (402 feature_not_enabled); left out, it stays as it is. |
--address <value> | The agent's own address, which never changes: any other address answers 400. Accepted so a body that names it alongside forwarding works. |
--forwarding | Forwarding mode, for an address on your own domain: the mail service hosting it forwards its mail to the agent's forwarding_address, so no MX record changes. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails agents update hello --status paused
mails agents update hello --daily-send-limit nullmails agents archive
Archive an agent (its address is kept and cannot be reused). Also: mails agents delete, mails agents rm.
Soft-deletes the agent (sets status to archived). The address is retained and cannot be reused, and archiving cannot be undone. The agent sends nothing more; mail that reaches it is still received, stored and sent to your webhooks as before, so none is lost.
| Argument | Description |
|---|---|
<id> | Required. Agent id or name. |
mails agents archive support --yesmails agents verify-forwarding
Send a test through an agent's forward, to mark it verified.
For an agent in forwarding mode: sends one test email from mails.ai to the agent's own address. When your mail service forwards it to the agent's forwarding_address, it is dropped there (the agent never sees it) and forwarding_status becomes verified; if it has not arrived 15 minutes after it was sent, failing. Answers the agent at once: poll GET /v1/agents/{id}.
| Argument | Description |
|---|---|
<id> | Required. Agent id or name. |
mails agents verify-forwarding supportmails agents inbox
Show an agent's inbox summary and unread count.
Unread count, thread and unread counts per folder and per label, and the time of the latest message.
| Argument | Description |
|---|---|
<id> | Required. Agent id or name. |
mails agents inbox helloThreads
Conversations: status, labels, read state, folders.
mails threads list
List conversation threads. Also: mails threads ls.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--agent-id <value> | Filter by agent id or name. A key tied to one agent always reads that agent's mail, and naming another agent is a 403. Also: --agent. |
--status <value> | Filter by thread status (open|closed|archived). |
--folder <value> | Which folder's threads to return, or all for every folder. Without it: every folder except trash. One of: inbox, archive, spam, trash, sent, all. |
--read | false for unread threads only, true for read ones. |
--label <value> | Only threads carrying this label (a name, or a label id lbl_…). |
--mode <value> | live for threads that hold live mail, test for threads of test mail only (test_mode: true), all for both. One of: live, test, all. |
--q <value> | Case-insensitive substring of the subject or participant addresses. For word search across all mail, use GET /v1/search. |
--all | Fetch every page, not just the first. |
mails threads list --agent-id hello
mails threads list --status open --jsonmails threads get
Show a thread with its messages.
| Argument | Description |
|---|---|
<id> | Required. Thread id (thrd_…). |
mails threads get thrd_01JABCmails threads update
Change a thread's status, labels, read state or folder.
Changes read, folder, status, labels and label_ids; emits thread.updated when something changed. folder: "archive" and status: "archived" move together, and moving an archived thread anywhere else reopens it. folder: "sent" is a 422: a thread is filed there while every message in it is one the agent sent. So is status: "archived" with any folder but archive.
| Argument | Description |
|---|---|
<id> | Required. Thread id. |
| Flag | Description |
|---|---|
--labels <a,b,…> | Label names, up to 50, each at most 64 characters. Replaces the thread's labels. Any such name works; a name that matches a label object picks up its id and colour. |
--label-ids <a,b,…> | Label object ids (lbl_…). Replaces the thread's labels with those labels' names. Sent together with labels, the thread gets both. Up to 50. |
--status <value> | "archived" also files the thread in archive; "open" or "closed" on an archived thread moves it back to the inbox. One of: open, closed, archived. |
--read | Mark the thread read (true) or unread (false). |
--folder <value> | Move the thread to inbox, archive, spam or trash. archive also sets status "archived", and moving an archived thread anywhere else reopens it. One of: inbox, archive, spam, trash. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails threads update thrd_01JABC --status closed --labels resolved,shippingmails threads mark-read
Mark a thread as read.
| Argument | Description |
|---|---|
<id> | Required. Thread id. |
mails threads mark-read thrd_01JABCmails threads mark-unread
Mark a thread as unread.
| Argument | Description |
|---|---|
<id> | Required. Thread id. |
mails threads mark-unread thrd_01JABCmails threads restore
Move a thread back to the inbox.
| Argument | Description |
|---|---|
<id> | Required. Thread id. |
mails threads restore thrd_01JABCmails threads spam
Move a thread to spam.
| Argument | Description |
|---|---|
<id> | Required. Thread id. |
mails threads spam thrd_01JABCmails threads delete
Move a thread to trash, or delete it for good with --permanent. Also: mails threads rm, mails threads trash.
Moves the thread to trash (deleted: false, the thread returned). Sent again for a thread already in trash it changes nothing, so a retried request never destroys mail. With permanent=true it deletes the thread for good, from any folder: its messages' content, raw email and attachments are removed, and thread.deleted is emitted. Trash is also emptied automatically 30 days after a thread was moved there.
| Argument | Description |
|---|---|
<id> | Required. Thread id. |
| Flag | Description |
|---|---|
--permanent | true deletes the thread for good instead of moving it to trash. |
mails threads delete thrd_01JABC
mails threads delete thrd_01JABC --permanent --yesDrafts
Compose, edit, schedule and send drafts.
mails drafts list
List drafts. Also: mails drafts ls.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--agent-id <value> | Filter by agent id or name. A key tied to one agent always reads that agent's mail, and naming another agent is a 403. Also: --agent. |
--status <value> | Filter by draft status. |
--all | Fetch every page, not just the first. |
mails drafts listmails drafts create
Save a draft, or schedule it with --send-at.
Creates a draft, or a scheduled draft if send_at is in the future. Scheduling sends the draft at that time, so a send_at needs the send scope: a key with only draft gets 403 insufficient_scope. A draft cannot carry attachments (422 invalid_field, param attachments); send files with POST /v1/messages.
| Flag | Description |
|---|---|
--agent <value> | Which agent sends the draft: its name or agt_ id. |
--to <a,b,…> | Required. Who the draft goes to: one address, or a list of 1 to 50. It is stored and returned as a list. |
--cc <a,b,…> | Addresses to copy when the draft is sent, up to 50. |
--bcc <a,b,…> | Addresses to blind-copy when the draft is sent, up to 50. They appear in none of the email's headers. |
--subject <value> | Subject line, 1 to 998 characters. A draft without one is sent with an empty subject. |
--text <value> | Plain-text body, up to 1,000,000 characters. A draft can be saved without a body. Also: --body-text. |
--text-file <value> | Read the plain-text body from a file (- for stdin). |
--html <value> | HTML body, up to 1,000,000 characters. A draft can be saved without a body. Also: --body-html. |
--html-file <value> | Read the HTML body from a file (- for stdin). |
--reply-to <value> | The address replies to the sent draft should go to. Left out, replies go to the agent's receiving_address. |
--in-reply-to-message-id <value> | Id of the message this one answers (msg_… or rcv_…), named in the email's In-Reply-To header. |
--send-at <value> | Schedule the draft to send at this time: an ISO 8601 UTC time ending in Z, in the future (400 invalid_field otherwise). It needs the send scope. |
--tag <name=value> | Up to 10 name-value labels, copied onto the message when the draft is sent; they are not part of the email. Also: --tags. |
--list-unsubscribe | Add one-click unsubscribe (RFC 8058), for newsletters and digests. One click suppresses the address workspace-wide. Only for exactly one recipient, with no Cc or Bcc. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails drafts create --to jordan@example.com --subject "Following up" --text "Just checking in."mails drafts get
Show one draft.
| Argument | Description |
|---|---|
<id> | Required. Draft id (dft_…). |
mails drafts get dft_01JABCmails drafts update
Edit a draft.
Edits a draft or scheduled draft. Setting a future send_at schedules it; clearing it returns to draft. A draft that starts sending while it is being changed answers 409 duplicate_resource. Without the send scope a key can neither schedule a draft nor change a scheduled one, except to unschedule it with send_at: null (403 insufficient_scope).
| Argument | Description |
|---|---|
<id> | Required. Draft id. |
| Flag | Description |
|---|---|
--to <a,b,…> | Replaces the recipients: one address, or a list of up to 50. Left out, they stay as they are. |
--cc <a,b,…> | Replaces the Cc list, up to 50 addresses; [] empties it. Left out, it stays as it is. |
--bcc <a,b,…> | Replaces the Bcc list, up to 50 addresses; [] empties it. Left out, it stays as it is. |
--subject <value> | Replaces the subject, 1 to 998 characters. Left out, it stays as it is. |
--text <value> | Replaces the plain-text body, up to 1,000,000 characters. Left out, it stays as it is. Also: --body-text. |
--text-file <value> | Read the plain-text body from a file (- for stdin). |
--html <value> | Replaces the HTML body, up to 1,000,000 characters. Left out, it stays as it is. Also: --body-html. |
--html-file <value> | Read the HTML body from a file (- for stdin). |
--send-at <value> | A future time (ISO 8601, UTC, ending in Z) schedules the draft, and null unschedules it back to draft; left out, the schedule stays as it is. |
--list-unsubscribe | Add one-click unsubscribe (RFC 8058), for newsletters and digests. One click suppresses the address workspace-wide. Only for exactly one recipient, with no Cc or Bcc. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails drafts update dft_01JABC --subject "Quick follow-up"mails drafts delete
Delete a draft. Also: mails drafts rm.
Deletes a draft. A draft that is being sent cannot be deleted (409 duplicate_resource), and a test key cannot delete one a live key made (403 live_mode_required).
| Argument | Description |
|---|---|
<id> | Required. Draft id. |
mails drafts delete dft_01JABC --yesmails drafts send
Send a draft now, or schedule it with --send-at.
Sends a draft immediately, or schedules it when the request body supplies a future send_at. A test key cannot send or schedule a draft a live key made (403 live_mode_required).
| Argument | Description |
|---|---|
<id> | Required. Draft id. |
| Flag | Description |
|---|---|
--idempotency-key <value> | Replays the stored response for a repeated key (kept 24 hours), so a retried request never double-sends; the replay carries Idempotent-Replay: true. |
--send-at <value> | Send the draft at this time instead of now: an ISO 8601 UTC time ending in Z, in the future (400 invalid_field otherwise). Left out, the draft is sent at once. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails drafts send dft_01JABC
mails drafts send dft_01JABC --send-at 2026-12-01T09:00:00ZLabels
Labels for threads.
mails labels list
List labels. Also: mails labels ls.
Every label in the workspace (at most 200), by name.
mails labels listmails labels create
Create a label.
A name (unique in the workspace, ignoring case) and a colour from a fixed palette of 12.
| Argument | Description |
|---|---|
<name> | Required. Unique within the workspace, ignoring case. 1-64 characters. |
| Flag | Description |
|---|---|
--color <value> | The label's colour, one of 12 palette names. Left out, it is gray. One of: gray, red, orange, amber, yellow, green, teal, cyan, blue, indigo, purple, pink. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails labels create billing --color bluemails labels get
Show one label.
| Argument | Description |
|---|---|
<id> | Required. Label id (lbl_…). |
mails labels get lbl_01JABCmails labels update
Rename or recolor a label.
Renaming a label renames it on every thread that carries it.
| Argument | Description |
|---|---|
<id> | Required. Label id (lbl_…). |
| Flag | Description |
|---|---|
--name <value> | Renaming a label renames it on every thread that carries it. |
--color <value> | A new colour from the same palette of 12. Left out, the colour stays as it is; a request must carry name, color or both. One of: gray, red, orange, amber, yellow, green, teal, cyan, blue, indigo, purple, pink. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails labels update lbl_01JABC --name invoicesmails labels delete
Delete a label. Also: mails labels rm.
Deletes the label and takes it off every thread that carries it.
| Argument | Description |
|---|---|
<id> | Required. Label id (lbl_…). |
mails labels delete lbl_01JABC --yesSearch
Search received and sent email.
mails search
Search received and sent email.
Full-text search over received and sent mail (subject, body text and addresses), newest first. Each hit says whether it was received or sent and carries a snippet around the match. Never leaves the workspace; a key tied to one agent searches that agent's mail; a test key searches test mail only and a live key live mail only.
| Argument | Description |
|---|---|
[q] | Full-text search: every word must appear in the subject, body or addresses, as a whole word or, from three letters up, the start of one (inv finds invoice). |
| Flag | Description |
|---|---|
--agent-id <value> | Filter by agent id or name. A key tied to one agent always reads that agent's mail, and naming another agent is a 403. Also: --agent. |
--from <value> | The sender's address contains this text (the display name, which the sender chooses, is not matched). |
--to <value> | A recipient address contains this text. |
--after <value> | Only mail at or after this time (ISO 8601, or YYYY-MM-DD). |
--before <value> | Only mail before this time (ISO 8601, or YYYY-MM-DD). |
--has-attachment | true for mail with a file attachment, false for mail without. |
--folder <value> | Which folder's mail to return, or all for every folder. Without it: every folder except trash. One of: inbox, archive, spam, trash, sent, all. |
--label <value> | Only mail in threads carrying this label (a name, or a label id lbl_…). |
--type <value> | Only received or only sent mail. One of: received, sent. |
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--all | Fetch every page, not just the first. |
mails search "refund"
mails search invoice --from billing@acme.com --has-attachment --jsonEvents
Every event, and a live tail.
mails events list
List events. Also: mails events ls.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--event-type <value> | Filter by exact event type. |
--agent-id <value> | Filter by agent id or name. Also: --agent. |
--since <value> | Only events created at or after this time: an ISO 8601 time such as 2026-10-05T07:00:00Z or 2026-10-05T16:00:00+09:00, or a date (2026-10-05, midnight UTC). |
--all | Fetch every page, not just the first. |
mails events list --event-type message.received --limit 5mails events get
Show one event.
| Argument | Description |
|---|---|
<id> | Required. Event id (evt_…). |
mails events get evt_01JABCmails events redeliver
Send an event to your webhooks again.
Re-dispatches a stored event to all matching webhook endpoints. Needs a key that is not tied to an agent, since an endpoint can receive every agent's mail (403 insufficient_scope).
| Argument | Description |
|---|---|
<id> | Required. Event id. |
mails events redeliver evt_01JABCmails events tail
Print events as they happen. Also: mails events listen, mails events stream.
Follows the workspace's live event stream and prints one line per event (one JSON object per line when piped or with --json). It reconnects by itself and never misses or repeats an event. Stop it with Ctrl-C, --count or --timeout.
| Flag | Description |
|---|---|
--type <a,b,…> | Only these event types. A trailing * matches a family, e.g. message.* Also: --event-types, --types. |
--since <value> | Start after this event id, or from the position a stopped run printed, instead of from now. |
--from-start | Replay the workspace's whole event history first. |
--position-file <value> | Start where the last run that used this file stopped, and keep the place saved in it: runs one after another miss no event and repeat none. |
-n, --count <n> | Stop after this many events. |
--timeout <n> | Stop after this long (exit 0). |
mails events tail
mails events tail --type message.received,reply.received
mails events tail --json --count 1 --timeout 60 | jq .typeWebhooks
Endpoints, deliveries, and forwarding events to localhost.
mails webhooks list
List webhook endpoints. Also: mails webhooks ls.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--all | Fetch every page, not just the first. |
mails webhooks listmails webhooks create
Register an HTTPS endpoint to receive events.
Registers an HTTPS endpoint to receive events. The signing_secret is returned ONCE — use it to verify signatures. An empty event_types list is refused with 422 invalid_param_value: an endpoint with no event types would never be sent anything. Closed to apps connected by sign-in (403 connected_app_not_allowed): use the dashboard, an API key or the mails CLI.
The signing secret is shown once, in this result. Keep it: it is how your handler verifies deliveries.
| Argument | Description |
|---|---|
<url> | Required. The https:// URL events are POSTed to. Its host must resolve to public IP addresses only. |
| Flag | Description |
|---|---|
--event-types <a,b,…> | Events to deliver, at least one. "*" is every event except thread.created, thread.updated, thread.deleted and message.delayed, sent only to an endpoint that names them. Up to 50. |
--description <value> | A note of your own to tell endpoints apart, up to 255 characters. Left out, description is null. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails webhooks create https://api.acme.com/hooks/mails --event-types message.received,reply.receivedmails webhooks get
Show one webhook endpoint.
| Argument | Description |
|---|---|
<id> | Required. Webhook endpoint id (whe_…). |
mails webhooks get whe_01JABCmails webhooks update
Change a webhook endpoint, or switch it off.
Changes an endpoint's URL, event types, description or active state. An empty event_types list is refused with 422 invalid_param_value, as on create. An app connected by sign-in may change everything except url (403 connected_app_not_allowed).
| Argument | Description |
|---|---|
<id> | Required. Webhook endpoint id (whe_…). |
| Flag | Description |
|---|---|
--url <value> | A new https:// URL for the endpoint, checked as on create. The signing secret stays the same, and retries still due go to the new URL; left out, the URL is unchanged. |
--event-types <a,b,…> | Events to deliver, at least one. "*" is every event except thread.created, thread.updated, thread.deleted and message.delayed, sent only to an endpoint that names them. Up to 50. |
--description <value> | Replaces the endpoint's note, up to 255 characters; left out, it is unchanged. null is refused: clear it with an empty string, which is returned as it is. |
--active | false pauses the endpoint: nothing is delivered to it, events raised meanwhile are not sent when it resumes, and a retry that comes due is dead-lettered. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails webhooks update whe_01JABC --active falsemails webhooks delete
Delete a webhook endpoint and its delivery history. Also: mails webhooks rm.
Deletes the endpoint and its delivery history.
| Argument | Description |
|---|---|
<id> | Required. Webhook endpoint id. |
mails webhooks delete whe_01JABC --yesmails webhooks rotate-secret
Issue a new signing secret (the old one keeps working for 24 hours).
Issues a new signing secret, returned once. For the next 24 hours every delivery's X-Mails-Signature carries a v1 signature from the new secret and one from the previous secret, so a receiver can switch over without failing a delivery. Verify by accepting the header when any v1 matches. Rotating again within the 24 hours retires the oldest secret.
| Argument | Description |
|---|---|
<id> | Required. Webhook endpoint id. |
mails webhooks rotate-secret whe_01JABCmails webhooks deliveries
List delivery attempts for a webhook endpoint.
| Argument | Description |
|---|---|
<id> | Required. Webhook endpoint id. |
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--all | Fetch every page, not just the first. |
mails webhooks deliveries whe_01JABC --limit 20mails webhooks test
Send a test event to a webhook endpoint.
Fires a synthetic webhook.test event at the endpoint and reports the delivery result.
| Argument | Description |
|---|---|
<id> | Required. Webhook endpoint id. |
mails webhooks test whe_01JABCmails webhooks replay
Send the event behind a past delivery again.
Re-sends the event behind a previous delivery attempt to that delivery's endpoint only. Other endpoints subscribed to the event are not sent it again; to re-send an event to every subscribed endpoint, use POST /v1/events/{id}/redeliver.
| Argument | Description |
|---|---|
<delivery-id> | Required. Webhook delivery id (whd_…). |
mails webhooks replay whd_01JABCmails webhooks listen
Forward live events to a local address, signed like real deliveries. Also: mails webhooks forward.
Posts every event to the address you give (for example your handler on localhost), with the same body and the same X-Mails-Signature, X-Mails-Event-Id and X-Mails-Event-Type headers a real webhook delivery carries. No tunnel and no public address needed. Sign with your endpoint's real secret via --secret-env, or let the CLI make one for this session and print it for your handler to use.
| Flag | Description |
|---|---|
--forward-to <value> | Required. Where to post each event, e.g. http://localhost:3000/webhooks/mails Also: --to, --url. |
--events <a,b,…> | Only these event types: without it, what an endpoint on * is sent. A trailing * matches a family (message.*), which a real endpoint's event_types cannot. Also: --type, --event-types. |
--secret-env <value> | Sign with the secret held in this environment variable (default MAILS_WEBHOOK_SECRET when it is set). |
-H, --header <key=value> | Extra header to send with each forward. Repeat for several. |
--since <value> | Start after this event id, or from the position a stopped run printed, instead of from now. |
--from-start | Replay the workspace's whole event history first. |
--position-file <value> | Start where the last run that used this file stopped, and keep the place saved in it: runs one after another miss no event and repeat none. |
-n, --count <n> | Stop after this many events. |
--timeout <n> | Stop after this long (exit 0). |
mails webhooks listen --forward-to http://localhost:3000/webhooks/mails
mails webhooks listen --forward-to http://localhost:3000/hook --events message.received,reply.received
MAILS_WEBHOOK_SECRET=… mails webhooks listen --forward-to http://localhost:3000/hook # reuse your endpoint's secretMAILS_WEBHOOK_SECRET is set (or --secret-env names another variable), each forwarded event is signed with a secret the CLI makes for this session and prints. Set MAILS_WEBHOOK_SECRET to your endpoint’s real signing secret instead, so the handler you test here verifies exactly as it will in production.Domains
Your own sending domains and their DNS records.
mails domains list
List your domains. Also: mails domains ls.
mails domains listmails domains create
Add your own sending domain and get the DNS records to create. Also: mails domains add.
Registers a bring-your-own sending domain (paid plans). Returns the DNS records to create at your registrar — records-only verification, no nameserver changes. Agents whose address is on a VERIFIED custom domain send under their own identity instead of the shared workspace subdomain. A domain that is already added, to this workspace or to another, is refused with 400 duplicate_resource.
| Argument | Description |
|---|---|
<domain> | Required. The domain to send from, such as mail.acme.com, 4 to 253 characters, lowercased and without a trailing dot. |
| Flag | Description |
|---|---|
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails domains create mail.acme.commails domains get
Show a domain and its DNS records.
| Argument | Description |
|---|---|
<id> | Required. Domain id (dom_…). |
mails domains get dom_01JABCmails domains delete
Remove a domain. Also: mails domains rm.
Agents with addresses on this domain stop sending: every send from them is refused with 422 domain_not_verified until the domain is added and verified again, or the agent is recreated on your workspace's mails.ai subdomain. It is never sent from another domain instead.
| Argument | Description |
|---|---|
<id> | Required. Domain id (dom_…). |
mails domains delete dom_01JABC --yesmails domains verify
Check a domain's DNS records now.
Re-checks every DNS record and updates per-record + overall status. Each call is an attempt: one that does not pass counts toward failed, 20 in a row mark the domain failed, and the count starts over on success or after a day without a call. Polling is rarely needed.
| Argument | Description |
|---|---|
<id> | Required. Domain id (dom_…). |
mails domains verify dom_01JABCAPI keys
Create, list and revoke API keys.
mails api-keys list
List API keys (prefixes only). Also: mails api-keys ls.
Lists the workspace's API keys, newest first, without their plaintext. A key tied to one agent lists only the keys tied to that same agent.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--all | Fetch every page, not just the first. |
mails api-keys listmails api-keys create
Create an API key.
Creates a new API key. The plaintext key is returned ONCE in this response and never again. Closed to apps connected by sign-in (403 connected_app_not_allowed): use the dashboard, an API key or the mails CLI.
The API shows a new key only once, so say where it goes: --save-as <profile> stores it as a profile, --key-file <path> writes it to a private file, --show-key prints it. Without one of them nothing is created.
| Flag | Description |
|---|---|
--name <value> | A label for the key, 1 to 80 characters, returned as name wherever the key is listed. It need not be unique; left out, name is null. |
--agent-id <value> | Tie the key to one agent. |
--scopes <a,b,…> | send sends mail, read reads it, and draft writes drafts but can neither send nor schedule one. One of: send, read, manage, draft. |
--mode <value> | live makes an mk_live_… key that sends real mail, and test an mk_test_… key whose sends are stored and raise events but are never transmitted or billed. Default live. One of: live, test. |
--expires-at <value> | When the key stops working: an ISO 8601 UTC time ending in Z. From then on it answers 401 expired_api_key; left out, the key never expires. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
--save-as <value> | Store the new key as this saved profile (only its prefix is shown). |
--key-file <value> | Write the new key to this file, readable only by you (only its prefix is shown). |
--show-key | Print the whole new key, once. |
mails api-keys create --mode test --save-as test
mails api-keys create --name "Production server" --scopes send,read --key-file ./mails.key
mails api-keys create --name "CI" --show-keymails api-keys revoke
Revoke an API key at once. Also: mails api-keys delete, mails api-keys rm.
Revokes a key immediately. Idempotent — revoking an already-revoked key returns its existing revoked_at. A test key cannot revoke a live key (403 live_mode_required): revoke it with a live key or in the dashboard.
| Argument | Description |
|---|---|
<id> | Required. API key id. |
mails api-keys revoke key_01JABC --yesConnected apps
List and revoke the apps connected by signing in.
mails connected-apps list
List the apps connected by signing in, this CLI among them. Also: mails connected-apps ls.
The apps connected to this workspace by signing in, whoever in it connected them: the mails CLI and apps such as Claude, ChatGPT or Cursor. Only those that can still act are listed, newest first: one revoked, or whose tokens have all expired or been signed out, is not. Settings › Connected apps in the dashboard shows the same apps to everyone in the workspace.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--all | Fetch every page, not just the first. |
mails connected-apps listmails connected-apps revoke
Revoke a connected app at once. Also: mails connected-apps delete, mails connected-apps rm.
Ends the sign-in, as Revoke in Settings › Connected apps does: every access and refresh token it was issued stops working at once, and the app has to be approved again to get back in. The API keys and webhook endpoints it created, and the webhook signing secrets it was shown, stay; the response counts them, so revoke and rotate those yourself.
API keys and webhooks the app created, and webhook signing secrets it was shown, stay: the result counts them, so revoke and rotate those too.
| Argument | Description |
|---|---|
<id> | Required. The connected app's id (oag_…), from List connected apps. |
mails connected-apps revoke oag_01JABC --yesSuppressions
Addresses that must not be mailed, and the allow list.
mails suppressions list
List allow entries, or check one address with --address. Also: mails suppressions ls.
Three answers from one route. With ?address=, the suppression status of that address. With ?view=suppressed, which of your own recent recipients are suppressed for you, and why (up to 500, in one page; addresses are stored hashed, so the list itself cannot be browsed). Without either, the workspace allowlist (paginated).
| Flag | Description |
|---|---|
--address <value> | Look up suppression status for this address instead of listing the allowlist. |
--view <value> | suppressed: list which of your own recent recipients are suppressed for you, and why, instead of the allowlist. One of: suppressed. |
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--all | Fetch every page, not just the first. |
mails suppressions list
mails suppressions list --address jordan@example.commails suppressions allow
Record that a suppressed recipient has agreed to receive mail again.
Attests that a recipient consents, overriding non-hard-bounce suppression for that address.
| Argument | Description |
|---|---|
<address> | Required. The recipient to send to again despite a complaint or unsubscribe; a hard bounce is not lifted. |
| Flag | Description |
|---|---|
--attestation <value> | Required. Your statement that the recipient agreed to receive your mail, 20 to 2,000 characters. It is stored with the entry and returned with it. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails suppressions allow jordan@example.com --attestation "Reconfirmed opt-in in account settings on 2026-06-20."mails suppressions revoke
Remove an allow entry. Also: mails suppressions delete, mails suppressions rm.
| Argument | Description |
|---|---|
<id> | Required. Allowlist entry id (sup_…). |
mails suppressions revoke sup_01JABC --yesMetrics
Counts over time.
mails metrics
Sent, delivered, bounced, complained, received and reply counts over time.
Counts of sent, delivered, bounced, complained, received and reply messages per day or hour (UTC). Defaults to the last 30 days by day, or the last 48 hours by hour; at most 366 days or 744 hours per call. A test key counts test mail only and a live key live mail only.
| Flag | Description |
|---|---|
--from <value> | Start of the range (ISO 8601, or YYYY-MM-DD). |
--to <value> | End of the range, exclusive (ISO 8601, or YYYY-MM-DD). |
--agent-id <value> | Filter by agent id or name. A key tied to one agent always reads that agent's mail, and naming another agent is a 403. Also: --agent. |
--interval <value> | Bucket size. One of: day, hour. Default: day. |
mails metrics --from 2026-10-01 --to 2026-10-08 --interval dayLogs
Audit log.
mails logs
Show the audit log of changes made in the workspace.
Read-only audit trail of state-changing operations, and of requests the API refused with a 4xx once it knew the workspace (category api, action request.refused). A refusal before that, such as a missing or unknown key, is not recorded. The same refusal again (the same key, method, path and code) is recorded at most once a minute, a 429 once a minute per code, and a workspace records at most 30 refusals a minute.
| Flag | Description |
|---|---|
--limit <n> | Page size (1–100). Default: 25. |
--cursor <value> | Opaque cursor from a previous page's next_cursor. |
--category <value> | Filter by audit category. |
--action <value> | Filter by audit action. |
--all | Fetch every page, not just the first. |
mails logs --limit 20
mails logs --category api_key --jsonUsage
Usage this billing period.
mails usage
Show this billing period's usage against the plan's limits.
Returns metered usage for the current billing period plus the workspace's tier caps. A key tied to one agent cannot read it (403 insufficient_scope): usage and costs are counted for the whole workspace, and GET /v1/metrics counts that agent's mail.
mails usageReputation
Sender reputation.
mails reputation
Show sender reputation for the workspace or one agent.
Workspace-wide reputation when called without agent_id, or per-agent stats when an agent id/name is supplied.
| Flag | Description |
|---|---|
--agent-id <value> | Agent id or name. Omit for the workspace aggregate. Also: --agent. |
mails reputation
mails reputation --agent-id helloBilling
Subscription.
mails billing portal
Get a link to manage or start the subscription.
Returns a Stripe Billing Portal URL when already subscribed, otherwise a Checkout URL to subscribe. A workspace on an active paid plan with no Stripe subscription on file gets 409 billing_state_inconsistent instead of a Checkout URL, since subscribing again would bill it twice: contact support@mails.ai.
| Flag | Description |
|---|---|
--return-url <value> | Where the customer lands after leaving the billing portal, or after finishing or cancelling checkout: an absolute URL. Left out, it is the dashboard's /billing page. |
--price-lookup-key <value> | The plan checkout subscribes to: mailsai_pro_monthly (the default), mailsai_scale_monthly, mailsai_pro_yearly or mailsai_scale_yearly. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails billing portalHealth
API status.
mails health
Check that the API is up.
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.
mails healthUtility
Checks, shortcuts, and setting up AI clients.
mails send
Send an email.
Sends an email from one of your agents. With a mk_test_ key the message is stored and its message.sent event reaches your webhooks (marked test_mode: true), but no email is sent and nothing is billed. Quota, suppression and rate-limit refusals are skipped, and a message the cold-email firewall would refuse comes back 201 with classifier_warning instead.
| Flag | Description |
|---|---|
--idempotency-key <value> | Replays the stored response for a repeated key (kept 24 hours), so a retried request never double-sends; the replay carries Idempotent-Replay: true. |
--agent <value> | Which agent sends this — its name or agt_ id. |
--from <value> | The sender you want, as a handle or address — billing, billing@acme.com and Acme <billing@acme.com> all mean the agent called billing. |
--to <a,b,…> | Required. Who the message goes to: one address, or a list of 1 to 50. |
--cc <a,b,…> | Addresses to copy, up to 50. Every recipient sees them. |
--bcc <a,b,…> | Addresses to blind-copy, up to 50. They receive the message but appear in none of its headers. |
--subject <value> | Required. Subject line, 1 to 998 characters. |
--text <value> | Plain-text body, up to 1,000,000 characters. At least one of body_text and body_html is required, or their aliases text and html. Also: --body-text. |
--text-file <value> | Read the plain-text body from a file (- for stdin). |
--html <value> | HTML body, up to 1,000,000 characters. With body_text as well, the email carries a plain-text part and an HTML part. Also: --body-html. |
--html-file <value> | Read the HTML body from a file (- for stdin). |
--reply-to <value> | The address replies should go to. Left out, replies go to the agent's receiving_address. |
--in-reply-to-message-id <value> | Id of the message this one answers (msg_… or rcv_…), named in the email's In-Reply-To header. The send joins its thread when the same agent sent or received it. |
--references <a,b,…> | Message-IDs for the References header. Write one from another system with its angle brackets (<…>); a value without them is read as a mails.ai message id. |
--attach <file> | A local file (about 3 MB in all, sent inside the request) or an https URL that mails.ai fetches (25 MB in all). Repeat for several. Not with a future --scheduled-at. Up to 10. Also: --attachment, --attachments. |
--inline <file> | A file to show inside the HTML, as id=file or id=https-url: <img src="cid:id"> in --html shows it. Repeat the flag for several. |
--metadata <key=value> | Your own key-value strings, kept with the message and returned by GET /v1/messages/{id}; they are not part of the email. Keys up to 64 characters, values up to 500. |
--scheduled-at <value> | Send at this time (ISO 8601) instead of now. A time in the past sends at once. A future time cannot be combined with attachments. |
--tag <name=value> | Up to 10 name-value labels, kept with the message and included in its message.sent event; they are not part of the email. Also: --tags. |
--list-unsubscribe | Add one-click unsubscribe (RFC 8058), for newsletters and digests. One click suppresses the address workspace-wide. Only for exactly one recipient, with no Cc or Bcc. |
-d, --data <value> | The request body as JSON (- reads it from stdin). Flags given alongside override its fields. |
--body-file <value> | Read the JSON request body from a file (- for stdin). |
mails send --to jordan@example.com --subject "Your order shipped" --text "It is on its way."
mails send --from hello --to a@example.com,b@example.com --subject "Hello" --html-file ./hello.html --attach ./invoice.pdf
cat message.json | mails send --data -mails doctor
Check the CLI, your sign-in and the connection, and say how to fix what is wrong.
Checks the Node version, the CLI version, where your credential comes from, whether the API answers, your clock, what your key is allowed to do, and whether your custom domains are verified. Exits 1 when something is broken.
mails doctor
mails doctor --jsonmails mcp install
Add the mails.ai MCP server to Claude, Claude Code, Cursor, Codex or VS Code. Also: mails mcp add.
Connects an AI client to the hosted mails.ai MCP server, so it can send and read email with your workspace. By default the client signs in through your browser; with --auth key the config names the MAILS_API_KEY environment variable instead. A key is never written into a config file. Any file this changes is copied to a .bak-<time> file first. --print shows what would be done and changes nothing.
| Flag | Description |
|---|---|
-c, --client <value> | Required. Which client to set up. One of: claude, claude-code, cursor, codex, vscode. |
--scope <value> | user: for you, everywhere (default). project: a file in this folder that the team can share. One of: user, project. |
--auth <value> | oauth: sign in through the browser. key: read MAILS_API_KEY from the environment. auto (default) uses the browser when the server offers it. One of: auto, oauth, key. |
--with-key | Same as --auth key. |
--print | Show the config, the command and the next steps; change nothing. |
--name <value> | What to call the server in the client (default mails). |
--url <value> | The MCP address (default: the hosted server for your API address). |
mails mcp install --client claude-code
mails mcp install --client cursor --scope project
mails mcp install --client codex --auth key
mails mcp install --client vscode --printmails open
Open the dashboard (or one of its pages) in your browser.
| Argument | Description |
|---|---|
[page] | One of: dashboard, agents, emails, messages, threads, drafts, events, webhooks, domains, api-keys, keys, suppressions, logs, billing, settings, mcp. Default: dashboard. |
| Flag | Description |
|---|---|
--no-browser | Only print the address. |
mails open
mails open api-keys
mails open webhooks --no-browsermails docs
Open the documentation, or the API reference.
| Argument | Description |
|---|---|
[topic] | api for the API reference, openapi for the OpenAPI document. Default: the docs. |
| Flag | Description |
|---|---|
--no-browser | Only print the address. |
mails docs
mails docs api
mails docs openapi --no-browsermails update
Check for a newer CLI and print the command that installs it. Also: mails upgrade.
Asks npm for the newest version. It only prints the command to run: the CLI never changes its own files.
mails updatemails completion
Print a shell completion script (bash, zsh or fish).
The script asks the CLI itself for the words that fit, so it always matches the installed version: commands, flags, allowed values and your profile names.
| Argument | Description |
|---|---|
<shell> | Required. bash, zsh or fish. |
source <(mails completion zsh)
mails completion bash >> ~/.bashrc
mails completion fish > ~/.config/fish/completions/mails.fishmails commands
Print every command (--json: the whole tree with flags and examples, for agents).
With --json (or when piped) this is the machine-readable map of the CLI: every command with its arguments, flags, types, allowed values, examples, the API route behind it and the key scope it needs. Give a few words to see only that part, e.g. mails commands emails.
| Argument | Description |
|---|---|
[prefix…] | Only commands under these words. |
mails commands
mails commands --json
mails commands webhooks --json | jq '.commands[].command'Global options
Every command accepts these. --dry-run acts only on the commands that call one API route.
| Flag | Description |
|---|---|
--json | Print JSON. Automatic when stdout is not a terminal (--no-json keeps the table). |
-q, --quiet | No status lines or notes; implies --json. |
-p, --profile <value> | Use this saved profile (default: the active one, or MAILS_PROFILE). |
--api-key <value> | API key for this one command (beats MAILS_API_KEY and profiles). |
--api-url <value> | API address (default https://api.mails.ai, or MAILS_API_URL). |
-y, --yes | Confirm a destructive command without asking. |
--dry-run | Print the HTTP request instead of sending it. |
--debug | Log every HTTP request and response status to stderr (credentials masked). |
--color | Force color on (--no-color turns it off; NO_COLOR is honoured). |
-h, --help | Show help for the command. |
-v, --version | Print the CLI version. |
Environment variables
| Variable | What it does |
|---|---|
MAILS_API_KEY | API key to use (beats saved profiles). |
MAILS_API_URL | API address (default https://api.mails.ai). |
MAILS_BASE_URL | The same, under the SDKs' name for it, read when MAILS_API_URL is not set. |
MAILS_PROFILE | Saved profile to use. |
MAILS_CONFIG_DIR | Where profiles and the credentials file live. Without it: %APPDATA%\mails on Windows, else $XDG_CONFIG_HOME/mails when that is set (to an absolute path), else ~/.config/mails. |
MAILS_CREDENTIAL_STORE | auto, keychain or file. |
MAILS_WEBHOOK_SECRET | Signing secret for mails webhooks listen. |
MAILS_HTTP_TIMEOUT | Seconds before a request gives up (default 60). |
NO_COLOR | Turns color off. |
Was this page helpful?