What your agent can do today
- Send mail from an address on your own domain through fm.send(SendOptions), with typed SendResult and typed errors.
- Use a revocable, domain-bound key — the blast radius of a mistaken call is one mailbox, and you can revoke it in the dashboard without rotating anything upstream.
- Read the result through the API. Inbound mail is queryable against the same scoped endpoint the send call uses, so "did my verification link arrive?" has an answer the agent can read.
- Stay inside your zone. The from address must land on the key's bound domain, so the agent cannot send as someone else by guessing.
- Wait for a specific mail with a durable wait_for_email (fm.waits.create) — it answers immediately and resolves when the message lands, without holding an HTTP request open. Poll it with fm.waits.get.
- Read the inbound log with a cursor (fm.events.list), filtered by recipient, sender or subject. The event carries routing facts plus the subject and never a body; the body is an explicit fm.messages.get.
- Poll a send's delivery state with fm.messages.status — sent / delivered / bounced / failed, plus the SMTP Message-ID.
- Reply in thread by naming the parent (send({ replyToMessageId })); the SDK derives In-Reply-To and References so threads don't fork.
- Create a mailbox from code — fm.addresses.create(domainId, "billing"). It is the one account-configuration write the agent surface has: idempotent, one local part, and it never changes a domain's routing mode. Everything else about account setup stays dashboard-only.
- Call it over MCP — a hosted MCP server speaks MCP over streamable HTTP and exposes ten tools, each one checked against a per-tool scope before it runs. A scoped credential that may read inbound mail cannot also send.
- Drive it from a shell — the flowmails CLI: login, test, addresses, watch and wait. watch and wait resume from their cursor after a disconnect, so a dropped connection never loses a message.
- Look back and read the thread — fm.messages.search(domainId, { q }) for the mailbox history, and fm.threads.get(threadId) for the conversation a message belongs to.
- Send exactly once on a retry — send({ idempotencyKey }) makes a retry replay the first answer instead of sending twice, and a send that exceeds the account’s quota is refused with a typed 429 rather than sent.
- Scope the credential — issue a scoped credential in Settings → Agent Credentials with only the capabilities the agent needs. The raw key is shown once, only its hash is stored, and revoking it takes effect immediately.
Not shipped yet — roadmap, not vaporware
- Delivery-status webhooks — roadmap, not shipped yet. The signed webhook itself fires for message.received and message.sent today; the delivered and bounced half needs the receive worker to report its status transitions — it has no bounce or DSN ingestion today, so a bounce is not observable at all.
- Event-driven agent workflows — roadmap, not shipped, and deliberately so: the platform keeps the event log and the durable wait, and your agent's own code does the matching, extraction and action. Flowmails ships no rule engine, by decision rather than by omission.
The shipped MCP surface — the ten tools, their arguments and the scope each one needs — is documented on the API page. Delivery-status webhooks and event-driven workflows are the items still on the roadmap page.