Skip to content

PATTERN Cited by 1 source

Sub-addressed agent instance

Problem

A single agent class on a single domain needs to host many distinct per-conversation / per-ticket / per-tenant agent instances, each with its own persisted state — but provisioning a separate mailbox per instance is prohibitive (cost, DNS mess, human maintenance).

Pattern

Use RFC-5233 plus-sub-addressing (local+sub@domain) so the local-part selects an agent class and the plus-sub segment selects a specific instance inside that class. The inbound dispatcher parses the address, splits local and sub, and routes to { className: local, instanceName: sub }.

support@domain            → SupportAgent / default instance "support"
support+ticket-123@domain → SupportAgent / instance "ticket-123"
support+ticket-456@domain → SupportAgent / instance "ticket-456"
billing+invoice-789@domain → BillingAgent / instance "invoice-789"

"You can even use sub-addressing (support+ticket-123@…) to route to different agent namespaces and instances" (Source: sources/2026-04-16-cloudflare-email-service-public-beta-ready-for-agents).

Zero mailbox provisioning — one domain, one MX record, infinitely many distinct DO-backed agent instances.

Canonical shape

Agents SDK exposes createAddressBasedEmailResolver(className) which consumes the inbound To: address and returns { className, instanceName }:

import { routeAgentEmail } from "agents";
import { createAddressBasedEmailResolver } from "agents/email";

export default {
  async email(message, env) {
    await routeAgentEmail(message, env, {
      resolver: createAddressBasedEmailResolver("SupportAgent"),
    });
  },
} satisfies ExportedHandler<Env>;

Email to support+ticket-123@your-agents.example is dispatched to the SupportAgent Durable Object with instance-name "ticket-123".

Properties inherited

Because each sub-addressed instance is a separate DO:

  • Independent statethis.setState(...) only affects that instance's memory; tickets don't cross-contaminate.
  • Independent concurrency — single-writer per instance; messages to ticket-123 and ticket-456 are serialised independently.
  • Per-instance agent memory — the inbox thread + the DO state together form a per-conversation memory primitive.
  • One-to-one agent instance shape on the email tier — each address = one DO instance, the same economics bet Cloudflare applies at the HTTP, WebSocket, search-index, and Git-repo tiers.

Variants

  • Class + instancesupport+ticket-123@ (most common shape).
  • Class + namespace + instancesupport+team-alpha+ticket-123@ — parse multiple plus-sub segments into nested namespaces.
  • Opaque instance tokensupport+AbC123XyZ@ where the token is a signed payload (combines this pattern with patterns/signed-reply-routing-header).
  • Hash prefixsupport+h7a9@ as a shortened hash of an internal instance ID; useful when instance IDs are opaque UUIDs that would be ugly in an email address.

Trade-offs

Pro:

  • Zero provisioning overhead — one MX record, infinite addresses.
  • Human-readablesupport+ticket-123@ documents itself.
  • Standard RFC-5233 — supported natively by Cloudflare Email Routing, Gmail, Outlook, iCloud, many on-prem MTAs.
  • Composes with DO per-instance semantics — address parse → DO-ID, no lookup table.

Con:

  • Plus-sub stripping — not every MTA preserves the segment on reply. Some strip it, some rewrite it. Fresh mail is safe; replies require either signed headers as the authoritative routing source or a fallback resolver.
  • Address-as-key is public — anyone who sees support+ticket-123@domain learns the ticket ID. For sensitive opaque identifiers, use a signed / hashed token instead of the raw ID.
  • Domain squatting on sub-segment semantics — if the sub-segment format changes later, old signed links may break. Version the sub-segment format from day one (v1+ticket-123 or similar) to allow future migrations.

When to apply

  • Any agent with a natural per-conversation / per-ticket / per-tenant decomposition.
  • When provisioning a fresh mailbox per conversation would be prohibitive.
  • When the address itself is an acceptable place for a short human- or machine-readable identifier.

When not to apply

  • If the sub-segment would leak sensitive data (customer IDs, medical record numbers) — use a signed / hashed token instead.
  • If every MTA in your reply chain strips plus-sub — use signed inReplyTo headers as the authoritative reply-routing source.

Seen in

Last updated · 200 distilled / 1,178 read