Skip to content
DocsAgent inbox

Examples

Agent inbox

Official Kody doc

The homepage Trigger it card labeled Email is this example. Every Kody account already has an address. Mail that arrives there is stored and emits email.message.received. "Forward it to Kody" is a valid trigger for people and systems that can send email but cannot call an API.

This is not a "chat with Kody" inbox. You still chat with an agent. The inbox is a door into a package you own. See Text your agent if the question is "can my spouse text Kody?"

Live public behavior: @kentcdodds/grok-bot ./handle-email-message-received. Mail to kentcdodds+patch@inbox.kody.codes (and the same plus-tag on retired inbox hosts) wakes that bot with a thread briefing. Cold mail from strangers is ignored. Unaliased mail returns { handled: false } so another subscriber can still run.

The storage contract is Email primitives. The event contract is Subscriptions and events.

The address

  • Default inbox: {username}@<platform domain>. On production that is {username}@inbox.kody.codes.
  • Plus-tags work: {username}+invoices@inbox.kody.codes and {username}+patch@inbox.kody.codes both land in {username}'s inbox. The full tagged address is preserved on the stored message, so a handler can dispatch on the tag.
  • The inbox is provisioned automatically. There is nothing to create.
  • emailSend from a job or export only mails verified destinations on the account. The From address is always {username}@<platform domain>.

Forward a thread, mail a plus-tag from a form, or BCC the inbox from a system that already speaks SMTP. Kody stores the message, then subscribers run.

Package shape

Subscribe in package.json#kody.subscriptions:

{
	"name": "@you/inbox-router",
	"kody": {
		"subscriptions": {
			"email.message.received": {
				"handler": "./src/on-email-message-received.ts",
				"description": "Route stored inbound mail by plus-tag."
			}
		}
	}
}

The handler receives metadata, not the full body:

type EmailMessageReceivedEvent = {
	event: 'email.message.received'
	message: {
		id: string
		from_address: string | null
		to_addresses: Array<string>
		subject: string | null
	}
}

export default async function onEmailMessageReceived(
	event: EmailMessageReceivedEvent,
) {
	const to = event.message.to_addresses.join(',')
	if (!to.includes('+patch@')) return { handled: false }
	// Fetch the body only when this tag should act.
	return { handled: true, messageId: event.message.id }
}

Call emailMessageGet (or the package runtime email helper) only when the tag or sender says the message is yours to handle. Do not pull every body on every delivery.

After publish, smoke-test from interactive MCP with packageSubscriptionDispatch:

{
	"kody_id": "@you/inbox-router",
	"topic": "email.message.received",
	"email_message_id": "00000000000000000000000000000001"
}

Replay uses a stored inbound id. Fixture params are for synthetic envelopes. Pass exactly one of params or email_message_id.

Kent's grok-bot handler:

  • Wakes on you+patch@… when Kent is on the thread (From Kent, Kent on To/Cc, or a later reply on an opened thread).
  • Ignores cold mail from strangers.
  • Returns { handled: false } for unaliased mail so the Discord inbox subscriber still runs.
  • Accepts { dryRun: true } so you can prove routing without a wake.

Example prompts

Build one

Search Kody for agent inbox and email.message.received. I want a package that watches my Kody inbox and only handles mail to {my-username}+todo@inbox.kody.codes. On a match, store a one-line summary and mail me a notify-self note. Ignore everything else. After publish, smoke-test with packageSubscriptionDispatch on one stored message.

Wire a plus-tag to an existing agent

Look at https://kody.codes/@kentcdodds/grok-bot and the ./handle-email-message-received export. I want the same plus-tag wake on my account: mail to {my-username}+patch@inbox.kody.codes should brief my connected agent. Do not invent a "chat with Kody" inbox.

Prove it from chat

Import kody:@kentcdodds/grok-bot/handle-email-message-received with { dryRun: true, message: { from_address: "me@example.com", to_addresses: ["me+patch@inbox.kody.codes"], subject: "Fwd: please look" } } only if that package is already mine. Otherwise fork first. Show me the routing decision.

What you see

SurfaceWhat it is for
{you}@inbox.kody.codesThe address you give other people and systems
{you}+tag@inbox.kody.codesThe same inbox, with a tag the handler can branch on
/account/emailStored inbound and outbound copies, destinations, sender rules
/account/activitySubscription runs for email.message.received
Chat (execute)emailMessageSearch / emailMessageGet, or a dry-run of the handler

The homepage card is a tile with the kicker Email and the title Agent inbox. It links here.

Where to go next

Working with an agent? This page is also plain markdown at /docs/agent-inbox.md, or load it over MCP with search({ entity: 'guide:agent_inbox' }).