mailnix / Virtual inboxes

    MCP for AI agents

    Virtual inboxes

    A registered recipient pattern that acts as a saved filter, optional webhook, and optional forward over the soft-sandbox capture table. Built for AI test loops.

    What is it

    A virtual inbox is a project-scoped recipient pattern. When the soft-sandbox capture path writes a row to sandbox_messages, every inbox whose pattern matches its addresses_seen or intended_to receives that row. Multi-match is intentional; one send can land in several inboxes.

    Pattern shapes

    • "*": catch-all (auto-provisioned per project).
    • "@example.com": any address at that domain (case-insensitive).
    • "user@example.com": exact address (case-insensitive on both parts).

    Agent pattern: wait-for-message test loops

    Use virtual_inbox_message_wait instead of polling messages_list. The tool blocks server-side until a matching capture arrives, ortimeout_seconds elapses (default 30, max 120). On timeout it returns { timeout: true }; on capture it returns the same shape as virtual_inbox_message_get.

    agent: virtual_inbox_create(address_pattern="me@mysite.com")
           → { id: "inbox-uuid" }
    agent: writes contact-form.html that posts to /v1/messages
    agent: virtual_inbox_message_wait(
             id_or_address="me@mysite.com",
             timeout_seconds=120
           )                                              # blocks here
    user submits the form; the capture path inserts a row,
    the in-process pub/sub publishes on the (project, inbox) topic
    agent's wait unblocks, receives the message synchronously
    agent → user: "Got it. Subject 'X', body matched. Form is working."

    REST surface (mirrors MCP)

    GET    /v1/inboxes                       list project inboxes
    POST   /v1/inboxes                       create
    GET    /v1/inboxes/lookup?address=...    by pattern
    GET    /v1/inboxes/{id}                  detail
    PATCH  /v1/inboxes/{id}                  update mutable fields
    DELETE /v1/inboxes/{id}                  delete
    GET    /v1/inboxes/{id}/messages         list captures
    GET    /v1/inboxes/{id}/messages/{id}    message detail
    POST   /v1/inboxes/{id}/wait?since=&timeout=30  long-poll

    Notify webhooks

    When notify_webhook_url is set, each matching capture fires a single POST to that URL with a Mailnix-Virtual-Inbox-Signature header (Stripe-compatible HMAC-SHA256: t=<unix>,v1=<hex>). The shared signing secret is rotatable via the SES_API_VIRTUAL_INBOX_HMAC_SECRET env var; when unset, a per-inbox derived key (sha256("vinbox:" + inbox_id)) signs instead.

    Delivery is best-effort: single attempt, no retry. For durable customer subscriptions with retry-and-backoff, use the outbound-webhook surface POST /v1/webhooks.

    Virtual inboxes vs inbound routes

    Two unrelated systems. Inbound routes handle REAL incoming mail at *@inbound.mailnix.ch (durable, retried). Virtual inboxes observe CAPTURED outbound sends from the soft-sandbox path (best-effort). A real email never lands in a virtual inbox; a captured send never fires an inbound-route webhook.

    Full reference

    The complete contract lives at VIRTUAL_INBOXES.md.