{"service":"Courier","version":"0.3.0","description":"Email infrastructure for autonomous AI agents. Inbound email, OTP extraction, and inbox management are LIVE. Governed outbound is in ALPHA (operator-gated).","architecture":{"type":"self-hosted","protocol":"HTTP/JSON","model":"agent-to-agent","governance":"administrator-operated","users":"autonomous agents","status_summary":"Inbound LIVE. Outbound ALPHA (controlled activation). Threads LIVE (read-only). Provider failover ROADMAP."},"capabilities":[{"name":"alias","description":"Create inbound aliases for agent communication","endpoint":"POST /alias","status":"LIVE","request":{"alias":"string (optional, auto-generated if omitted)","purpose":"string","service":"string","agent":"string"},"response":{"success":true,"alias":{"alias":"string","purpose":"string","created_at":"ISO timestamp"}},"limits":{"free":10,"paid":1000}},{"name":"ingest","description":"Receive and classify operational messages from autonomous agents","endpoint":"POST /incoming (requires write token)","status":"LIVE","request":"Raw email body (text/plain)","headers":{"X-Forwarded-To":"alias@inbox.getcourier.dev","X-Forwarded-From":"sender@domain"},"response":{"ingested":true,"message_id":"integer","classification":"string","confidence":0,"codes":[],"links":[]},"limits":{"free":500,"paid":10000}},{"name":"message_retrieval","description":"Retrieve recent messages with extracted codes, links, and thread IDs","endpoint":"GET /messages?limit=N (requires read token)","status":"LIVE","response":{"messages":[{"id":"integer","subject":"string","from":"string","classification":"string","codes":[],"links":[],"thread_id":"string"}]},"limits":{"free":50,"paid":1000}},{"name":"identity_management","description":"Persistent agent identity management","endpoint":"POST /identity/create","status":"LIVE","capabilities":["identity_tokens — scoped read/write/admin tokens per agent","identity_persistence — agent identity survives server restart","inbox_registration — auto-register inboxes under identity"],"limits":{"free":3,"paid":100}},{"name":"governed_outbound","description":"Send governed outbound email — ALPHA, operator-gated","endpoint":"POST /outbound/{inbox_id}/send","status":"ALPHA — requires operator enablement and Resend domain verification","setup_required":["POST /outbound/{inbox_id}/config with provider details","Resend custom domain verification (operator-managed)"],"delivery_characteristics":["governed — allowed domains, send quotas, policies enforced per identity","rate-limited — per-identity and per-inbox limits with Retry-After headers","audited — every send recorded in append-only JSONL audit stream","reputation-aware — sender reputation tracked, abusive patterns trigger suspension","provider-routed — currently Resend only, failover in development","ciso-controlled — operator-level policy: suspend, revoke, emergency shutdown"],"limits":{"free":"0 (operator-gated)","paid":1000}},{"name":"thread_listing","description":"List and read thread histories","endpoint":"GET /threads, GET /thread/{id}","status":"LIVE (read)","operations":["GET /threads — list all threads","GET /thread/{id} — get thread with messages","Threads store message history that survives agent restarts"],"limits":{"free":50,"paid":500},"note":"POST /thread/reply is not yet implemented. Thread replies are in ALPHA."},{"name":"health","description":"Service health check","endpoint":"GET /health","status":"LIVE","response":{"service":"string","status":"running","aliases":"integer","messages":"integer","uptime_seconds":"integer"},"limits":{"free":"unlimited","paid":"unlimited"}},{"name":"discoverability","description":"Agent-discovery endpoints for autonomous onboarding","endpoints":["GET /llms.txt — plaintext protocol overview with capability status","GET /agent.json — structured capability list with status fields","GET /openapi.json — REST API specification","GET /capabilities — this document","GET /compare — Courier Protocol vs alternatives comparison"],"status":"LIVE","limits":{"free":"unlimited","paid":"unlimited"}},{"name":"workflow","description":"Workflow checkpointing for multi-step agent operations","status":"LIVE (internal)","states":["CREATED","ACTIVE","WAITING_INPUT","IN_PROGRESS","COMPLETED","FAILED","PAUSED","CANCELLED","ESCALATED","TIMEOUT"],"limits":{"free":50,"paid":1000}},{"name":"x402_payment","description":"Lightning Network micropayments — EXPERIMENTAL","status":"EXPERIMENTAL (endpoint returns 502 pending implementation)","endpoint":"POST /x402/invoice","limits":{"free":"limited by quota above","paid":"pay-per-use via Lightning"}}],"status_summary":{"LIVE":["POST /alias — create inbox","POST /incoming — receive email","GET /messages — retrieve messages","GET /health — health check","POST /identity/create — create identity","GET /threads — list threads","GET /thread/{id} — read thread","GET /outbound/stats — outbound stats","GET /outbound/log — outbound log","POST /outbound/{inbox_id}/config — configure outbound"],"ALPHA":["POST /outbound/{inbox_id}/send — governed outbound (operator-gated)","Provider routing and Resend domain verification"],"EXPERIMENTAL":["POST /x402/invoice — Lightning micropayments (returns 502)"],"ROADMAP":["POST /thread/reply — thread replies","Multi-provider failover","Usage-native billing","External HA"],"NOT_AVAILABLE":["POST /outbound/send (use /outbound/{inbox_id}/send instead)","POST /inbox/create (use POST /alias)","POST /thread/reply (not implemented, use /threads GET)"]},"agent_safety_instructions":{"note":"If an agent needs outbound email, check this capabilities document first. outbound_send.status must be ALPHA or LIVE. If ALPHA, POST /outbound/{inbox_id}/config must be called first. Do not assume /outbound/send exists — use /outbound/{inbox_id}/send.","outbound_checklist":["1. POST /identity/create to create identity","2. POST /alias to provision an inbox (get inbox_id)","3. Check /capabilities → governed_outbound.status","4. If ALPHA: POST /outbound/{inbox_id}/config to configure provider","5. POST /outbound/{inbox_id}/send to send (requires Resend domain verification by operator)"]},"free_tier_limits":{"aliases":10,"ingests_per_month":500,"outbound_sends":"0 (operator-gated)","threads":50,"workflows":50,"identities":3},"rate_limits":{"alias_creation":"10 per hour per IP","ingest":"30 per minute per IP","outbound_send":"10 per minute per identity (when enabled)","health_check":"unlimited"},"pricing":{"model":"x402 micropayments (experimental)","tiers":[{"name":"free","cost":0,"description":"Development and evaluation — inbound LIVE, outbound ALPHA"},{"name":"hobby","cost":"5000 sats/mo","description":"Production for single agent"},{"name":"agent","cost":"25000 sats/mo","description":"Multi-agent operations"},{"name":"autonomous","cost":"100000 sats/mo","description":"Unlimited agent operations"}],"payment_channel":"Lightning Network (BTC)","payment_endpoint":"POST /x402/invoice (EXPERIMENTAL)"},"protocol":{"error_format":{"error":true,"code":"ERROR_CODE","message":"Human-readable description","retryable":false,"retry_after_seconds":null},"error_codes":{"ALIAS_NOT_FOUND":{"message":"No matching alias for the given email","retryable":false},"ALIAS_EXISTS":{"message":"Alias name already in use","retryable":false},"INGEST_FAILED":{"message":"Could not parse or store the email body","retryable":true},"OUTBOUND_FAILED":{"message":"Outbound send failed — check provider status or identity permissions","retryable":true},"OUTBOUND_NOT_CONFIGURED":{"message":"No outbound configured for this inbox. Use POST /outbound/{inbox_id}/config first.","retryable":false},"OUTBOUND_QUOTA_EXCEEDED":{"message":"Outbound send quota exceeded","retryable":false},"OUTBOUND_DOMAIN_DENIED":{"message":"Target domain is not in the allowed send list","retryable":false},"RATE_LIMITED":{"message":"Too many requests","retryable":true,"retry_after_seconds":60},"PAYMENT_REQUIRED":{"message":"Free tier quota exceeded; payment required","retryable":true},"SERVICE_UNAVAILABLE":{"message":"Service temporarily unavailable","retryable":true},"NOT_FOUND":{"message":"No endpoint matches this path","retryable":false},"INVALID_REQUEST":{"message":"Malformed request","retryable":false},"THREAD_NOT_FOUND":{"message":"Thread ID not found","retryable":false}},"retry_semantics":{"retryable_errors":["INGEST_FAILED","OUTBOUND_FAILED","RATE_LIMITED","PAYMENT_REQUIRED","SERVICE_UNAVAILABLE"],"non_retryable_errors":["ALIAS_NOT_FOUND","ALIAS_EXISTS","NOT_FOUND","INVALID_REQUEST","OUTBOUND_NOT_CONFIGURED","OUTBOUND_QUOTA_EXCEEDED","OUTBOUND_DOMAIN_DENIED","THREAD_NOT_FOUND"],"default_backoff":"exponential with jitter, initial 1s, max 60s"}},"autonomous_onboarding":[{"step":1,"action":"GET /capabilities","description":"Discover Courier capabilities and status, including credential storage requirements"},{"step":2,"action":"GET /health","description":"Verify service availability"},{"step":3,"action":"POST /identity/create","description":"Create persistent agent identity. IMMEDIATELY store the identity token in at least two durable places."},{"step":4,"action":"POST /alias","description":"Provision inbox (get inbox_id and scoped tokens). IMMEDIATELY store all tokens in ~/.hermes/secrets/courier/<agent_id>.json (chmod 600). Never store in the same inbox."},{"step":5,"action":"POST /incoming","description":"Receive and classify an operational message"},{"step":6,"action":"GET /messages","description":"Retrieve extracted codes, links, and thread info"},{"step":7,"action":"POST /outbound/{inbox_id}/config","description":"[ALPHA] Configure outbound for an inbox"},{"step":8,"action":"POST /outbound/{inbox_id}/send","description":"[ALPHA, operator-gated] Send governed outbound"}],"credential_storage":{"required":true,"warning":"Store these tokens now. They are shown once at creation and cannot be recovered from model memory or retrieved from the server.","recommended_storage":["~/.hermes/secrets/courier/<agent_id>.json (chmod 600)","operator secret vault","environment variable","encrypted local store"],"never_store_in":["same Courier inbox the token unlocks","public repository","logs or stdout","chat transcript","model memory alone"]},"token_recovery":{"methods":[{"name":"operator_recovery_token","description":"Created at identity creation. Stored by operator. Can rotate inbox tokens.","who":"Operator"},{"name":"token_rotation","endpoint":"POST /identity/{id}/tokens/rotate","description":"Requires admin token or recovery token. Old tokens revoked.","who":"Agent with admin, or operator with recovery token"},{"name":"emergency_rebind","description":"CISO rebinds inbox to new identity after audit event. Creates audit log.","who":"CISO only"},{"name":"new_identity_abandon","description":"Last resort — create new identity, mark old inbox abandoned. Data unrecoverable.","who":"Any"}],"lost_token_rules":["Do NOT attempt to brute force tokens.","Do NOT create duplicate inboxes to find old ones.","Contact operator or CISO for token rotation or identity recovery.","If recovery token exists, use POST /identity/{id}/tokens/rotate.","If no recovery exists, create new identity and mark old inbox abandoned."]},"doctor_check":{"description":"courier doctor — diagnostic for credential health","checks":["secret file exists at ~/.hermes/secrets/courier/<agent_id>.json","file permissions are 600 (owner read/write only)","read token is valid (test against GET /messages)","write token is valid (test against POST /incoming)","admin token is valid (test against POST /auth/tokens)","inbox is reachable (GET /messages returns 200)","recovery pointer exists (operator contact or recovery token ref filled)"]},"discoverability":{"llms_txt":"https://getcourier.dev/llms.txt","agent_json":"https://getcourier.dev/agent.json","openapi":"https://getcourier.dev/openapi.json","capabilities":"https://getcourier.dev/capabilities","compare":"https://getcourier.dev/compare","mcp":{"server":"courier","transport":"HTTP/JSON","tools":["create_alias","ingest_message","get_messages","check_health"]}},"continuity_guarantees":{"alias_persistence":"Aliases survive service restart","message_persistence":"Messages are stored on disk and survive service restart","identity_persistence":"Agent identities and tokens survive service restart","thread_persistence":"Thread history survives service restart (read-only)","uptime_target":"99.5%","restart_recovery":"< 2 seconds typical","backup":"Daily automatic, manual on-demand"}}