# Inbox A purpose-built email inbox for agents, backed by Cloudflare Email Service. Every active tenant can provision one inbox with the deterministic canonical address `@inbox.cohesivity.app`. Account owners can inspect claimed projects in a read-only dashboard viewer; composing and mutation remain agent/API work, with no SMTP or IMAP surface. ## Prerequisites Edge calls require this resource to be provisioned first; unprovisioned calls return an error. ### Provision curl -s -X POST https://cohesivity.ai/api/resources/inbox \ -H "Authorization: Bearer " ### Delete curl -s -X DELETE https://cohesivity.ai/api/resources/inbox \ -H "Authorization: Bearer " Provisioning happens once, before the application runs; the running application does not provision its own resources. > **Ephemeral contract.** The 72-hour tenant gets only its canonical address, send/receive/list/read/reply/delete, one recipient per outbound message, and five outbound emails total. Vanity and webhooks unlock after claim. There is no per-minute Inbox send cap. > **Server-side only.** `coh_application_key` is a secret; browser JS, mobile bundles, and other client-side code cannot hold it safely. This call belongs in a Railway-hosted server, `cloudflare-workers`, or your own server tier. See the canonical key-secrecy directive in `.cohesivity` for details. ## Storage - Provisioning Inbox ensures a shared tenant Neon project exists and returns the compatibility object `postgres: { required, auto_provisioned, schema }`; `postgres.auto_provisioned` reports whether that internal project was created by this call. This storage dependency does not provision or grant the public `postgres` resource; `/edge/postgres` remains unavailable until that offering is provisioned separately. - Normalized messages live under the private `coh_inbox.messages` schema. It is not accessible through `/edge/postgres`: that endpoint uses a dedicated public role restricted to the `public` schema, while Cohesivity keeps owner access for reserved `coh_*` schemas. - Complete inbound MIME, including attachments, remains in private Cohesivity R2. Attachment bytes are available only through the authenticated Inbox attachment endpoint. - Inbox, public Postgres, social-login event history, and realtime history can reuse the same project without granting one another's API surfaces. Inbox teardown drops only `coh_inbox`; the Neon project is deleted only when the last dependent is removed. ## Addresses and Vanity - Canonical address: `@inbox.cohesivity.app`. It remains an alias while the Inbox is active. - Claimed tenants may add one optional vanity: check with `GET https://cohesivity.ai/api/vanity/check?name=acme`, then claim once with `POST https://cohesivity.ai/api/vanity` and `{ "name": "acme" }` using the management key. The Inbox address becomes `acme@inbox.cohesivity.app`. - The same vanity is shared with hosting as `acme.cohesivity.app`. Inbox and hosting inherit whichever claim was created first. - A vanity is immutable: it cannot be renamed, released, or transferred. Reposting the same name is idempotent; a different name or any DELETE returns `409 vanity_immutable`. Account deletion tombstones the canonical and vanity identities so neither can ever be reassigned. ## Send ``` POST https://cohesivity.ai/edge/inbox/messages?key= Idempotency-Key: order-123-confirmation Content-Type: application/json { "to": ["person@example.com"], "cc": [], "bcc": [], "subject": "Hello", "text": "Plain text body", "html": "

Optional HTML body

" } ``` `Idempotency-Key` is required and may be 1-128 characters. Reusing it returns the original message without another provider call. The API ignores any caller-supplied `from`; Cohesivity forces the active tenant vanity address, or the canonical address when no vanity exists. `to`, `cc`, and `bcc` may be a string or string array, with at most 10 unique recipients combined. At least one of `text` or `html` is required. Outbound attachments are not included. A successful send returns HTTP 202 with `message.status: "accepted"` and the Cloudflare message id. `accepted` means Cloudflare accepted the message, not that the destination mailbox delivered it. If Cloudflare returns an ambiguous internal failure, Cohesivity stores `unknown`; reuse the same idempotency key and do not retry with a new one. ## Reply Reply to an inbound message through the same send endpoint. Cohesivity derives the recipient, sender alias, thread, `In-Reply-To`, and `References`; callers cannot override them. ``` POST https://cohesivity.ai/edge/inbox/messages?key= Idempotency-Key: reply-message-42 Content-Type: application/json { "reply_to_message_id": "", "text": "Thanks — received." } ``` Replies target inbound messages only. `subject` is optional for replies; absent values inherit `Re: `. ## Read and Delete - List newest first: `GET https://cohesivity.ai/edge/inbox/messages?key=&limit=50`. `limit` is 1-100. Continue with the opaque `next_cursor` as `cursor=...`; optionally filter one conversation with `thread_id=`. - Get one: `GET https://cohesivity.ai/edge/inbox/messages/?key=`. - Download an inbound attachment: `GET https://cohesivity.ai/edge/inbox/messages//attachments/?key=`. Attachment indexes come from the message `attachments` array. Attachments and HTML are untrusted email content; do not execute or render them without your own safety controls. - Delete one: `DELETE https://cohesivity.ai/edge/inbox/messages/?key=` → HTTP 204. This removes normalized data and private raw MIME immediately and decrements live storage. Messages expose `received`, `pending`, `accepted`, `failed`, or `unknown` status. Inbound messages are `received`. List/get never claim final outbound delivery. ## Inbound Webhook Claimed Inbox can send one signed `message.received` event after an inbound message and its durable tenant-Postgres outbox row exist. Ephemeral tenants cannot configure a webhook. Provisioning a claimed Inbox accepts optional `{ "webhook_url": "https://agent.example.com/hooks/inbox" }`; the provision response includes `webhook.secret` once. An existing claimed Inbox uses these management-key endpoints: - `GET https://cohesivity.ai/api/inbox/webhook` returns the active URL and event, never the secret. - `PUT https://cohesivity.ai/api/inbox/webhook` with `{ "url": "https://agent.example.com/hooks/inbox" }` creates or replaces the URL. A newly created configuration returns its secret once; URL replacement preserves the secret. - `POST https://cohesivity.ai/api/inbox/webhook/rotate-secret` returns a replacement secret once and invalidates queued events signed for the older configuration. - `DELETE https://cohesivity.ai/api/inbox/webhook` returns HTTP 204 and prevents queued events for that configuration from being delivered. Delivery is an HTTP POST with `Content-Type: application/json` and body `{ "type": "message.received", "message_id": "" }`. The headers are `webhook-id`, `webhook-timestamp`, and `webhook-signature`. The signature is `v1,` over `..` using the `whsec_...` secret. A receiver can reject timestamps outside its own tolerance before verifying the HMAC. Delivery is at-least-once. `webhook-id` is stable for a message, so receivers can de-duplicate it. A 2xx response acknowledges the event; redirects, timeouts, and other status codes retry at approximately 1, 5, 20, and 60 minutes, then move to Cohesivity's dead-letter queue after the fifth failed attempt. Inbox exposes no delivery-history or replay API; `GET /edge/inbox/messages` is the recovery feed. Webhook URLs must be public HTTPS URLs without embedded credentials. ## Limits and Retention - Inbound and outbound message ceiling: 5 MiB each. Cloudflare accepts larger inbound mail, but Cohesivity rejects it at 5 MiB to keep the API symmetric and bounded. - Outbound recipients: 1 per message while ephemeral; 10 total across to/cc/bcc after claim. - Private raw-MIME storage: Free 100 MB, Plus 1 GB, Pro 5 GB per inbox. Mail is rejected once the active inbox reaches its storage ceiling. - Retention: fixed 30 days for every tier. The lifecycle sweep removes normalized rows and raw MIME in bounded batches. - Ephemeral tenants get five outbound emails and five recipients for the entire tenant lifetime with no Inbox per-minute cap. Claimed monthly and per-minute buckets are shown below. Inbound mail does not consume outbound buckets. ## Error Contract - `400 idempotency_key_required`, `invalid_json`, `invalid_cursor`, or `invalid_thread_id`: repair the request; do not retry it unchanged. - `403 tenant_must_be_claimed`: claim the tenant before configuring Inbox vanity or a webhook. - `404 message_not_found`, `attachment_not_found`, or `reply_target_not_found`: the target does not exist for this tenant. - `409 inbox_not_active`, `inbox_storage_migrating`, or `reply_target_invalid`: Inbox must be active and on its tenant Postgres data plane; replies target inbound messages. - `413 message_too_large`: reduce the text/HTML payload below the 5 MiB ceiling. - `422 invalid_recipients`, `too_many_recipients`, `invalid_subject`, `invalid_content`, `invalid_reply_to_message_id`, or `invalid_webhook_url`: the submitted message or webhook URL is invalid. - `429`: a Cohesivity per-minute/monthly bucket or Cloudflare account send limit denied the call. Respect `Retry-After` when present. - `502 send_failed`: Cloudflare definitively rejected the send. `502 send_outcome_unknown` is ambiguous; retry only with the same `Idempotency-Key`. - `503 inbox_storage_not_configured` or `email_sending_not_configured`: Cohesivity provider configuration is unavailable; retry later. ## Dashboard Viewer Signed-in account owners can open `https://cohesivity.ai/account/inbox` to search and inspect projected sent/received message content across their claimed Inbox projects. The viewer renders plain text only, blocks HTML formatting and remote content, shows attachment metadata without downloads, and exposes no send, reply, forward, delete, or configuration controls. ## Deliberately Not Included No dashboard composer or message mutation controls, drafts, webhook delivery-history/replay API, SMTP, IMAP, custom mail domains, batch sends, arbitrary headers, arbitrary sender addresses, outbound attachments, or per-tenant Cloudflare routing rules. Cloudflare has one operational catch-all for `inbox.cohesivity.app`; provisioning and deprovisioning only change Cohesivity data. ## Rate Limits Ephemeral tenants pause as a whole if any authoritative hard cap below is exceeded. Claimed tiers use account-scoped buckets shared across every project owned by the Cohesivity user; OpenAI, AI Gateway, Deepgram, and Exa are fluid-only after tier, rate, and concurrency checks; AI Gateway and Deepgram have no fixed monthly usage bucket for claimed tiers. **Ephemeral** - emails: 5 per ephemeral tenant lifetime before claim or expiry - recipients: 5 per ephemeral tenant lifetime before claim or expiry **Claimed Free** - requests: 3 per minute - emails: 100 per month - recipients: 200 per month **Claimed Plus** - requests: 15 per minute - emails: 1000 per month - recipients: 2000 per month **Claimed Pro** - requests: 60 per minute - emails: 5000 per month - recipients: 10000 per month ### Notes - Ephemeral tenants get the canonical @inbox.cohesivity.app address, send/receive/list/read/reply/delete, one recipient per outbound message, and five outbound emails across the tenant lifetime. Ephemeral Inbox has no vanity address or webhook. - Claimed Inbox messages are capped at 5 MiB and each outbound send supports at most 10 total to/cc/bcc recipients. Private raw-MIME storage is capped at 100 MB on Free, 1 GB on Plus, and 5 GB on Pro; all tiers use fixed 30-day retention. - Outbound status `accepted` means Cloudflare Email Service accepted the message. It is not a final-delivery guarantee.