Security

This page lists only things that are implemented and tested. It is a description of the code, not a certification. The service is in early access (see status): there is no SOC 2 report, no published penetration test and no bug bounty.

Authentication#

AreaWhat happens
API keysGenerated from 24 random bytes, prefixed am_. Only a SHA-256 hash is stored, so the secret is shown once at creation and cannot be recovered. Keys can be limited to a pod or inbox and to named permissions.
Dashboard passwordsHashed with scrypt (N=2^15, r=8, p=1) and a per-user salt. A password change rotates the session.
Dashboard sessionsOpaque session ids stored hashed, in a cookie that is HttpOnly and SameSite=Lax, and Secure in production. Every state-changing session request needs a CSRF token and a same-origin Origin. API-key requests are not cookie based, so CSRF does not apply to them.
Two-step verificationTOTP (RFC 6238) with single-use recovery codes, which are stored hashed. Codes cannot be replayed. See two-step verification and login security. The dashboard shows the setup secret and an otpauth link, not a QR code.
Login protectionFailed logins are throttled per email and IP with a capped, growing delay so strangers cannot lock you out from elsewhere. Sign-ups, verification emails and agent sign-ups are rate limited too.
Email verificationSending and creating API keys are locked until the owner email is verified. Agent-created accounts are send-locked until a human verifies a 6-digit code (24 hour expiry, 10 attempts).
Reserved namesAddresses such as postmaster@ and noreply@ on the shared domain cannot be registered.

Isolation between customers#

  • Every query is scoped to your organization. Resources outside your key's scope answer 404 not_found, never 403, so their existence is not revealed.
  • A pod-scoped key reaches only that pod. An inbox-scoped key reaches only that inbox and cannot create inboxes, pods or domains. A key can never be given more permissions than the key that creates it.
  • Organization-level lists are not reachable with pod or inbox scoped keys.
  • The hosted MCP endpoint runs each tool call as an ordinary request carrying your own key, so permissions, scoping, plan limits and isolation are exactly the REST ones.
  • An independent read-only review checked isolation across threads, messages, drafts, lists, pods, domains, keys, search and the WebSocket and found no cross-tenant access; it did find and we fixed permission gaps on webhook, events, metrics and suppression routes.

Rate limits#

All limits are counted in memory per server process. Defaults:

LimitDefault
Requests per IP600 per minute
Requests per API key1200 per minute
/auth/* requests per IP30 per minute
Open-tracking pixel per IP3000 per minute (separate bucket)
Sends per inbox100 per rolling 24 hours
Recipients per message50

Limited requests get 429 rate_limited and a Retry-After header. Because the counters live in memory they reset when the server restarts and are not shared across servers; the service currently runs as a single server.

Webhooks#

  • Every delivery is signed. Headers svix-id, svix-timestamp and svix-signature carry an HMAC-SHA256 over id.timestamp.body, keyed with your whsec_ secret. Verify the raw body and reject timestamps more than five minutes old. A valid signature can be replayed inside that window, so dedupe on svix-id. See webhooks.
  • The signing secret is returned only when the webhook is created. Get, list and update never include it. Custom header values are write-only.
  • Webhook URLs must be public https URLs. Delivery resolves DNS inside the connection, blocks private, loopback, link-local and metadata addresses (so DNS rebinding does not help), does not follow redirects, times out after 10 seconds and caps the response it reads. Delivery logs record only coarse error classes.
  • Attachments fetched by url go through the same guard: public https only, 10 MB per file and 25 MB in total.

Data handling#

  • Where it lives. Mail and account data are stored in one SQLite database on a persistent disk attached to the service's single server. There is no region choice and no multi-region copy. The server refuses to start in production without a persistent disk, so a deploy cannot silently wipe accounts.
  • Backups. The database is copied online every 6 hours by default (VACUUM INTO) and the newest 14 copies are kept. The copies are written to the same disk. Copying them somewhere else is an operator task that is not automated by the code, so treat the disk as the failure domain until that is in place. A restore tool checks the backup with an integrity check before replacing the live database.
  • Logs. Request logs are JSON lines. Passwords, tokens, secrets, authorization headers and api_key query values are redacted. MCP request logs record the JSON-RPC method and tool name only, never arguments or mail content.
  • Open tracking. Off unless requested per message. The pixel stores an open count and first and last open time. It never stores the IP address or user agent. See open tracking.
  • Attachments. Downloads are served with Content-Disposition: attachment, X-Content-Type-Options: nosniff and Content-Security-Policy: sandbox. The dashboard never renders email HTML.

Transport and browser headers#

HSTS is sent in production. Responses carry X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer and a restrictive Content-Security-Policy. CORS is closed unless the operator lists origins. The /mcp endpoint refuses any request that carries an Origin header.

Reviews and known gaps#

Two independent reviews (Phase 3 and Phase 4) produced fixes with regression tests, among them server-side request forgery through attachment and webhook URLs, missing permission checks, sign-up takeover, quota counting per recipient, a metrics query that could stall the server, and a prompt-injection exfiltration path in the LangChain toolkit. The log is docs/SECURITY.md in the repository. Items we know about and have not fixed:

  • Rate limits, login throttles and sign-up limits are in memory (single server, reset on restart).
  • Custom headers on send are passed through, so Reply-To and Return-Path can be overridden by the sender. From cannot.
  • Mail received over the built-in SMTP receiver, if an operator enables it, carries no SPF or DKIM verdict. Production inbound arrives through the email provider's receiving.
  • Billing provider calls have been tested against simulated providers, not live test accounts yet.
  • Backups are not copied off the server by the application.

Reporting a problem#

A public security contact address has not been published yet. Until it is, report issues through the support contact you were given when you signed up. Do not put secrets in a report.