Skip to content

Extend

Program it. Don't configure it.

Every other inbox hands you a settings page and a fixed set of switches. Tinbox hands you the stack: Skills, plugins, agents, automations, and a sandboxed line to any system you run — all behind one typed manifest the chat, the CLI, and MCP share. The things your inbox can do are a property of your repo, reviewable in a PR. Teach it your business instead of bending your business to fit a vendor's roadmap.

Six building lanes

From a one-line parser to an outreach agent.

Six lanes, one question each: reach for the one that names your job. A parser reads mail; a script sends it; a plugin is a whole module; a view reshapes the app. Each has a docs page with a copyable example — and all of it runs on your own Worker, in your own account.

Parsers

The unit you'll reach for most: a rule that reads one inbound email, pulls out the fields that matter, and renders a card right in the thread. Draft one live from a real email in the in-app playground, no redeploy. See them render, with live cards →

Replies

Attach reply logic to a send itself: when they reply, draft an answer in your voice; if they don't, follow up on a schedule. Three scopes — a per-send directive, a per-inbox agent, an org-wide handbook — on one draft/send/ask dial.

Scripts

Your other apps talk to Tinbox over the SDK, CLI, and /mcp — one tbx_ token for all three. Send transactional mail, render stored templates per recipient, sync data, import a list — an ad-hoc mailer is 15 lines.

Plugins

Whole modules, not just hooks. A plugin mounts its own /plugins/… routes, runs cron tasks, taps inbound and outbound mail (onEmail, onSend, onSent), owns namespaced database tables, and holds its own scoped secrets.

Views

Render your own component into a named slot — a dashboard tile, a contact card — and give the assistant the query behind it as a tool it can call. Config-registered, deploy-time: the engine ships an id, your app owns the code.

Agents

Declare an agent per inbox, or set the autopilot loose on a schedule — you write the brief, pick the exact tools it may reach, and set the human-in-the-loop policy. The same tools power sourcing and outreach agents. The full agent model →

Config as code

One typed manifest, everywhere at once.

Your deployment is composed in a handful of app/ files — Skills, agents, plugins, and the autopilot, all typed and validated at build. Add a tool once and it shows up in the in-app chat, the CLI, and the /mcp endpoint together. Write it in one place; it's live in three.

And here's the argument the whole page hangs on: because it's checked-in code, the set of things your inbox can do is a property of your repo — reviewable in a PR, diffable over time, blamable to a commit. Nothing can quietly grant the inbox a new power; if it's not in the manifest, it can't happen. That's a guarantee a settings page can't make.

tinbox.config.ts
export default defineConfig({
  org: "acme",
  domains: ["acme.com"],

  // read · render · act on inbound mail
  skills: [...builtins, shopifyOrder, refundCard],

  // whole modules: routes, crons, hooks, DB
  plugins: [airtableLog(), signedWebhook()],

  // an agent, gated by capability tokens
  agents: defineAgents({ support: { /* … */ } }),
  autopilot: { enabled: true, tools: ["search_emails", "create_draft"] },
});

Scripts · ships today

A newsletter blast is fifteen lines.

Your Tinbox audience, crossed with your own data, rendered per recipient from a stored template, sent idempotently — from a plain script, no platform UI. The same tbx_ token reads the audience over a fully-typed API and sends the mail. This runs against a fresh instance unchanged.

  • · template + data render a stored template per recipient, server-side — no hand-built HTML.
  • · idempotencyKey makes a retry a safe no-op — send a digest at most once, per recipient, per week.
  • · It all runs on your own Cloudflare account — nothing transits a Tinbox relay.
weekly-digest.ts
import { Tinbox } from "tinbox/sdk";
const tb = new Tinbox({ host, apiKey });

// read a Tinbox audience over the typed API…
const { members } = await tb.api.audiences.members.query({ audienceId });

// …and render a stored template for each one.
await tb.emails.batch(members.map((m) => ({
  from: "Acme <news@acme.dev>",
  to: m.email,
  subject: "Your weekly digest",
  template: "weekly-digest", // rendered server-side
  data: { FNAME: m.name, TOP_PICK: m.topPick },
  idempotencyKey: `DIGEST-${weekId}-${m.email}`,
})));

Parsers · ships today

Ten lines, and it fires five ways.

A skill is a small TypeScript object. You declare which hosts it may call (it can reach no others), whether its effect can be undone, a match that decides which mail it runs on, and a run that returns a card. That one declaration is what makes it safe to run on every inbound email, as a tool the agent calls, as a thread button, on a schedule, or over a webhook.

  • · hosts pins the fetch — the skill physically cannot phone an address you didn't list.
  • · reversible: true lets autopilot run it unattended; drop it and every run defers to a human.
  • · run returns a card the inbox renders inline — live data in the thread, structured and clickable.

Or skip the file: in the in-app playground, paste a real email, describe what to pull out, and watch a parser extract the fields live before you turn it on — the agent-authored parser, no redeploy.

app/skills/shopify-order.ts
export default defineSkill({
  name: "shopify-order",
  reversible: true, // read-only → autopilot may run it
  hosts: ["{shop}.myshopify.com"], // fetch is pinned here

  // which mail it runs on
  match: (email) => /#(\d{4,})/.test(email.subject),

  // pull the live order, return a card
  async run(email, ctx) {
    const order = await ctx.fetch(orderUrl(email));
    return ctx.card("order", order);
  },
});

Change it your way

Tune it in Settings. Extend it in your code.

The dials your team touches day to day live in Settings, no deploy required. Teaching the inbox a brand-new trick is a commit you own — and that's the point: the things it can do are yours, not a vendor's roadmap.

At runtime, no redeploy

  • · Turn autopilot on or off, rewrite its brief, and pick which tools it may reach.
  • · Enable or disable a configured agent, and set its approval policy.
  • · Set integration secrets, switch the model per task, and tune the AI-writing voice.
  • · Rebrand the app — name, icon, theme — to your own.

In a commit you own

  • · Write a new Skill, plugin, or agent tool in TypeScript and deploy it to your Worker.
  • · Ship your own card components and the columns each inbox needs.
  • · Wire a button or a webhook into the systems you already run.
  • · Updates are a git pull you choose to take — the engine stays vendored underneath.

Straight talk: extending Tinbox means writing TypeScript and deploying it — there's no drag-and-drop builder, and custom UI is the cards and views you build into your own app layer, not a runtime plugin store. That's the trade for owning every line — and if you'd rather not write the code, we'll build your extensions for you.

Flexible, never out of control

Every extension runs behind the same gate as a human.

More power usually means more ways to get burned. Here it doesn't. Every Skill, plugin, and agent authenticates with a scoped, revocable token, checked by the same authorize() gate as every human action — a read-only token literally can't send. Reversible work (search, draft, render) flows freely; anything that sends or mutates the outside world is marked acting and defers to a human under autopilot. A plugin reaches only the hosts it declares and the secrets it's granted, over a fetch with a hard timeout.

The scoped-key model, in full — how an agent gets a key it can't escalate →

The whole stack is yours to shape.

Drop in a Skill, wire a plugin to your own systems, and ship an inbox that speaks your business — on your own account. The lifetime license adds the full readable source, yours to audit and modify, and the priority updates you git pull on your own schedule. This is the last email stack you'll have to extend by filing a feature request.