Skip to content

Core Concepts

The mental model behind Tinbox, from inboxes and threads to the git archive, audiences, campaigns, the send ledger, capability tokens, and the agent-first design.

Tinbox is a self-contained email stack you deploy to your own Cloudflare account: an inbox, a transactional sending API, and newsletters, with no third-party relay in the path. This page is the orientation map. Each concept is a short paragraph; the guides it links to go deeper.

Inboxes and routing

An inbox is an address that receives mail. Mail arrives through Cloudflare Email Routing, hits the ingest worker, and lands in the inbox whose address it was sent to. Routing is structural: which addresses exist and where mail lands is git-checked config, compiled into the worker bundle, changed by a pull request and a deploy. Granting a teammate access to an existing inbox is operational: that happens at runtime, instantly, through the API. The two are deliberately separate systems.

Threads and messages

A message is a single received or sent email. Tinbox groups messages into threads by owning the Message-ID, so replies stitch into a conversation deterministically. Threads carry shared-inbox workflow state (open / closed, assignment, unread) on top of the raw messages. Internal notes are a message kind that never leaves the app. Read a thread with tinbox thread <id> or the get_thread tool; list them with tinbox threads.

The git archive

Received and sent messages are immutable facts, so Tinbox projects them to a git repository: an append-only archive/. The sync only ever appends new files, so it cannot drift from the live state. Agents read mail history as plain files with ripgrep, no credentials needed. The other direction, outbox/, is a one-way authoring lane: PR-authored drafts and newsletters are born there and consumed into the database on merge. Git is never a two-way mirror of live state. The git-sync worker is an optional module.

Audiences, contacts, and members

An audience is a mailing list (the Mailchimp-style "list" concept). A contact is a person; a member is that contact's membership in one audience, with a status (subscribed, pending, unsubscribed, transactional) and per-audience merge fields. You populate audiences by importing existing lists or by capturing leads through hosted forms. Segments are saved multi-condition filters over an audience. See tinbox audiences and the list_audiences tool.

Campaigns vs. transactional sending

Two different jobs share one sending substrate. Transactional email is one-to-one, sent by your other apps through the sending API: receipts, password resets, alerts. Campaigns (newsletters) are one-to-many: authored, reviewed, then fanned out to an audience. Campaigns carry a review gate, analytics, and one-click unsubscribe that transactional sends do not. See Send a newsletter.

The send ledger and idempotency

Every send, a single transactional message or a 4,000-recipient campaign, writes a row to a ledger keyed by a stable idempotency key before dispatch, and checks the ledger before each send. Queues retry, merges can fire twice, CI re-runs; the ledger is what turns "retry-safe" into a guarantee instead of a hope. The sending API mints an idempotency key for you (or you pass your own); campaign fan-out keys one row per (campaign, recipient), so a re-run only enqueues un-sent rows. It never double-sends.

For transactional SDK sends, a caller-supplied key is trimmed and stored as the logical send id. A retry with the same key short-circuits a sent or pending row, but can reclaim a failed or canceled row. There is no client-side dedupe window: the ledger is the durable source of truth until rows are intentionally pruned by operations.

Capability tokens and roles

Every API call, from the app, the CLI, or an MCP agent, funnels through one authorize(caller, inbox, action) check. Access is two axes. Roles per inbox: viewer (read), agent (read + draft + reply), admin (+ grant others on that inbox). Capabilities granted explicitly on top: newsletter:send, api:send, import. A token (tbx_…) carries a role and coarse scopes; it gets no special powers over the human who minted it.

There are two common token families:

TokenCreated withGrants access to
Inbox agent tokentinbox tokens create --inbox …inbox mail: read threads, draft replies, send replies, MCP/agent workflows
Sending API keySettings → API keys, or tinbox tokens create-send-keytransactional sending through tinbox/sdk, scoped to an identity, domain, or account

Use the inbox token when a caller should operate on mailbox state. Use the sending API key when application code should send new transactional email.

Agent-first: every capability is a tool

Tinbox is built agent-first, human-supervised. Every capability is exposed as a tool in one shared manifest, consumed by three surfaces: the in-app chat assistant, the MCP server, and the tinbox CLI. Read-only tools (search, summarize, list) run freely; destructive ones (sending) are flagged so the client confirms first. Irreversible structure stays human-gated; reversible actions stay permission-gated. Browse them all in the tool reference.

Extend & automate

Tinbox is meant to be programmed. Past reading and sending mail, you teach a deployment new behavior with a small set of extension points. The rule of thumb: a skill annotates or acts on one resource (a message, a thread); a plugin is a bundle that adds a whole surface (routes, cron, send-hooks, its own tables).

I want to…UseWhere
Tag/annotate/draft on inbound maila reactive-mail skillconfig.skills
Give the AI assistant a new toolan agentic skill (agenticSkill())config.skills
Add a button to a threada command skill (commandSkill())config.skills
Customize the interactive chat assistantconfig.assistanttinbox.config.ts
Work the inbox on a scheduleautopilotconfig.autopilot + Settings → Autopilot
Run a dedicated autonomous agent on one inboxdefineAgentsconfig.agents
Add routes / cron / send-hooks / own tablesa pluginconfig.plugins
Wire a credentiala secret (requiresEnv)Settings → Secrets

The four AI surfaces are deliberately distinct: the assistant (config.assistant) is the interactive chat/MCP agent; autopilot (config.autopilot) is that assistant put on a cron with standing orders; agents (config.agents, defineAgents) are dedicated autonomous agents each bound to one inbox; and agentic skills are the tools all of them call. See Working with agents for the full breakdown.

Next steps