Mailgentic

Inboxes

An inbox is a real address that an agent can receive, inspect, organise and answer over the API. Each inbox belongs to a tenant and has its own threads, folders, messages, attachments, drafts and outbound policy.

Lifecycle

Method Path Access Purpose
POST /v1/tenants/{tenant_id}/inboxes admin / tenant admin Create an inbox
GET /v1/inboxes read List inboxes; filter with tenant_id
GET /v1/inboxes/{id} read Read configuration
PATCH /v1/inboxes/{id} admin / tenant admin Change display_name, status, policy, metadata
DELETE /v1/inboxes/{id} admin / tenant admin Soft-delete
POST /v1/domains/{id}/inbound admin / tenant admin Enable or disable receiving on a domain ({"enabled": true})

Create:

{
  "domain_id": "DOMAIN_ID",
  "local_part": "support",
  "display_name": "Support Agent",
  "policy": {
    "dlp": "enforce",
    "max_send_per_hour": 100,
    "allow_recipients": ["customer.example"],
    "require_approval_on_taint": true
  },
  "metadata": {"purpose": "customer-support"}
}

status is active or paused; a paused inbox refuses compose operations with 409 inbox_paused. provider is native or, for connected mailboxes, google / microsoft.

Lists are newest first; pass the last item’s created_at as cursor for the next page. limit defaults to 50, max 200.

Reading mail

GET /v1/inboxes/{id}/messages

Parameter Meaning
folder inbox (default view, excludes trash), sent, drafts, archive, spam, trash, a custom folder, or all
unread true → unread inbound only
direction in or out
label Exact label
from Case-insensitive substring on the sender
cursor An older message’s created_at
limit Default 50, max 200

A message:

{
  "id": "MESSAGE_ID",
  "inbox_id": "INBOX_ID",
  "thread_id": "THREAD_ID",
  "direction": "in",
  "from": "customer@example.net",
  "to": ["support@agents.example.com"],
  "subject": "I need help",
  "snippet": "I need help with...",
  "text": "I need help with my order.",
  "extracted_text": "I need help with my order.",
  "verdict": "clean",
  "auth": {"spf": "pass", "dkim": "pass", "dmarc": "pass"},
  "shield": {},
  "labels": [],
  "folder": "inbox",
  "read": false,
  "has_html": false,
  "attachments": [{"id": "ATT_ID", "filename": "receipt.pdf", "content_type": "application/pdf", "size": 48213}],
  "created_at": "2026-10-03T18:01:00Z"
}

extracted_text includes text pulled from attachments where possible, so a model can read a PDF invoice without a separate parsing step.

Verdicts

Every inbound message gets a Shield verdict: clean, suspicious, spam, blocked or unauthenticated. spam and blocked land in the spam folder automatically. The verdict is a safety signal, not permission: anything other than clean should be treated as untrusted data, and no verdict means the message’s instructions should be executed.

Threads, search, raw content

Method Path Purpose
GET /v1/inboxes/{id}/threads Threads, newest activity first (cursor = last_message_at)
GET /v1/threads/{id} One thread
GET /v1/threads/{id}/messages A thread’s messages
GET /v1/inbox-messages/{id} One message with attachment metadata
GET /v1/inboxes/{id}/search?q=… Full-text search over subject, body and extracted attachment text; optional folder, limit
GET /v1/inbox-messages/{id}/raw RFC 5322 source as message/rfc822
GET /v1/inbox-messages/{id}/html { "id", "html" } — returned as data, never rendered
GET /v1/inbox-attachments/{id} Attachment bytes (application/octet-stream, original type in X-Attachment-Content-Type)

Send, reply and forward

The inbox address is always the From; clients cannot override it. Outbound mail uses the same quotas, suppressions, DKIM and retries as relay sends, plus the inbox’s Shield policy.

Method Path Purpose
POST /v1/inboxes/{id}/messages Compose a new message
POST /v1/inbox-messages/{id}/reply Reply in the existing thread (reply_all: true to include original To/Cc)
POST /v1/inbox-messages/{id}/forward Forward; requires at least one to

Body (all three):

{
  "to": ["human@example.net"],
  "cc": [], "bcc": [],
  "subject": "Follow-up",
  "text": "Thanks for the details.",
  "html": "<p>Thanks for the details.</p>",
  "reply_all": false,
  "attachments": [{"filename": "details.pdf", "content_type": "application/pdf", "content": "<base64>", "content_id": "details", "inline": false}],
  "send_at": "2026-10-05T09:00:00Z"
}

At least one of text/html and one recipient. Attachments total ≤ 25 MiB. Replies default to the original sender and a Re: subject; forwards derive Fwd:.

Responses (all 202):

{ "message_id": "OUT_ID", "queued": 1, "thread_id": "THREAD_ID", "inbox_message_id": "SENT_COPY_ID" }
{ "held": true, "approval_id": "APPROVAL_ID", "reason": "dlp" }
{ "scheduled": true, "draft_id": "DRAFT_ID", "scheduled_at": "2026-10-05T09:00:00Z" }

Folders, moving, labels

System folders: inbox, sent, drafts, archive, spam, trash. Up to 100 custom folders (1–64 chars, case-insensitively unique, no /, \ or control characters).

Method Path Purpose
GET /v1/inboxes/{id}/folders Folders with total and unread
POST /v1/inboxes/{id}/folders { "name": "Invoices" }
PATCH / DELETE /v1/inboxes/{id}/folders/{name} Rename / delete (non-empty requires ?move_to=)
POST /v1/inboxes/{id}/folders/{name}/empty Permanently empty trash or spam
POST /v1/inbox-messages/{id}/move { "folder": "archive" }
DELETE /v1/inbox-messages/{id} To trash; from trash (or ?permanent=true) delete for good
POST /v1/inboxes/{id}/batch move, delete, read or unread on 1–200 IDs
POST /v1/inbox-messages/{id}/read { "read": true }
PUT /v1/inbox-messages/{id}/labels, /v1/threads/{id}/labels Replace labels

Nothing can be moved into sent or drafts. Unread mail in spam/trash does not count toward thread unread totals.

Drafts and scheduled send

Drafts live in drafts. Sending one removes it and creates the sent copy (or an approval if policy requires review). Scheduled drafts are sent by the server at send_at, through the same policy.

Method Path Purpose
GET / POST /v1/inboxes/{id}/drafts List / create (compose fields + optional reply_to_id, send_at)
GET / PATCH / DELETE /v1/drafts/{id} Read / replace / delete
POST /v1/drafts/{id}/send Send now
POST /v1/drafts/{id}/schedule Set or replace send_at
POST /v1/drafts/{id}/cancel Cancel the schedule, keep the draft

Events: message.draft_saved, message.scheduled, message.draft_sent.

AI auto-label and extraction

When AI is enabled on the account (PATCH /v1/account, ai_enabled), inbound mail can be auto-labelled and structured data extracted on demand:

curl -X POST "$API/v1/inbox-messages/$MESSAGE_ID/extract" \
  -H "Authorization: Bearer $AGENT_KEY" -H "Content-Type: application/json" \
  -d '{"schema": {"type":"object","required":["order_id"],"properties":{"order_id":{"type":"string"}},"additionalProperties":false}, "save": true}'

Message content is treated as untrusted data; model output is returned to you and cannot trigger actions. Available from the Developer plan with a monthly token cap (100k on Developer, 1M on Startup). Auto-enrichment emits message.enriched. The same capability is exposed as the extract_data MCP tool.

Connected Gmail and Microsoft 365

An inbox can be backed by a real Gmail or Microsoft 365 mailbox instead of the native store, so an agent works an existing human mailbox with the same API, folders, search, streams, Shield and IMAP access.

Method Path Purpose
POST /v1/inboxes/{id}/connect/google Returns auth_url; redirect the admin there
POST /v1/inboxes/{id}/connect/microsoft Same for Microsoft
DELETE /v1/inboxes/{id}/connect Disconnect, revoke tokens, revert to native

OAuth tokens are sealed with the account’s data key. Connected mailboxes are available on Startup and Enterprise plans.