Giving every customer their own agent inbox

Plenty of products are now "an agent that does X for your business", and every one of them hits the same wall: the agent needs to email your customer's customers, from an address that belongs to your customer, without any two tenants being able to see each other's mail.

5 min read

Tenants each holding their own identity inside one workspace boundary
One identity per tenant, provisioned when they arrive rather than when you get around to it.

That is a different problem from running four agents for yourself, and the difference is that the number is unbounded and decided by strangers at signup.

Provision at signup, not by hand

The rule that decides the architecture: an inbox has to appear the moment a tenant activates, without you touching anything. That means creating the identity from code, which is what POST /v1/signup and the identity APIs exist for, described in an agent that signs itself up.

Two shapes work, and the choice is mostly about who owns the domain.

One workspace, one identity per tenant. Simplest. Tenants are identities inside your workspace, each with its own key, mailbox, and grants. Isolation comes from identity scope: an identity cannot read another's mail unless granted, and you never grant it.

One workspace per tenant. Heavier, and correct when tenants need their own billing relationship, their own domain administration, or the ability to leave with their data and keep it running. The claim URL flow means a tenant can take ownership of the workspace you provisioned for them, which is the difference between an inbox you run and an inbox they own.

Start with the first unless a specific requirement pushes you to the second, because the migration from one to two is a data move, and the reverse is not.

Addresses, and the domain question

Three options, in increasing order of what the tenant has to do.

OptionAddress looks likeTenant effortWhen
Your subdomainacme@tenants.yourproduct.comNoneTrials, self-serve, day one
Their subdomain of yourshello@acme.yourproduct.comNoneWhen the address should read as theirs
Their own domainhello@agents.acme.comDNS recordsPaying customers who care

Start every tenant on the first so they are working immediately, and offer the third as an upgrade. The DNS work is the same set of records as anywhere, per SPF, DKIM, and DMARC for an agent subdomain, and it is the moment a tenant's mail starts building their reputation rather than yours.

That last point is the one worth understanding early. While tenants send from your domain, one tenant's behaviour affects every other tenant's delivery. That shared fate is acceptable for trials and unacceptable at scale, which is why moving real customers onto their own domains is a deliverability decision as much as a branding one.

Isolation is per identity, and you should test it

The isolation you are relying on is that a key belongs to one identity, and that identity's tools resolve only against its own mailbox, its granted notes, and its granted secrets. Contacts are the deliberate exception, being a workspace resource, which matters here: if tenants share a workspace, they share the address book.

That is usually wrong for a multi-tenant product, and it is the strongest argument for workspace-per-tenant. Decide it explicitly rather than discovering it when tenant A's agent greets tenant B's customer by name.

Whichever shape you choose, write a test that asserts it: create two tenants, have one's key attempt to read the other's mail, and assert the refusal. It is the single most valuable test in a multi-tenant email product and it takes twenty minutes, per test inboxes for agent development.

A key from one tenant refused against another tenant's mailbox
Assert the refusal in a test. Isolation you have not tested is isolation you are hoping for.

Limits, billing, and one tenant's bad week

Every tenant draws on allowances, and the two questions are whose allowance and whose bill.

If tenants are identities in your workspace, they share your daily and monthly send allowance, so one tenant's burst can exhaust the pool everyone draws from. That needs a per-tenant cap in your own code, enforced before the send, because the platform-level allowance does not know about your tenancy model. Set it low enough that a runaway tenant is throttled by you before it is refused by us, so the failure lands as your product saying no rather than as an opaque error.

If tenants have their own workspaces, the allowances are theirs, and the isolation extends to billing and the spend cap. That is cleaner, and it is why products with real usage tend to end up there.

Either way, meter per tenant from day one. Retrofitting per-tenant usage accounting onto a shared counter is unpleasant, and it is the number your own pricing will eventually depend on. The cost shape is worked through in what email actually costs.

Operational things that only appear at scale

  • Bursts are correlated. Tenants share a business day, so nine in the morning is everyone's peak. Per-thread serialisation and a concurrency cap are not optional, per what happens when forty messages arrive at once.
  • Suppression is per workspace. In a shared workspace, an address suppressed by one tenant's bounce is suppressed for all of them. Sometimes right, occasionally surprising, always worth knowing.
  • Support needs to see one tenant's threads without seeing everyone's, which is another reason the workspace boundary tends to win.
  • Tenant offboarding is a deletion path. Identity, mail, contacts, and any grants, on request and within a defined window, per the compliance floor.
  • A tenant's agent can be talked into things. Each one is an independent injection surface, and the blast radius should stop at that tenant, per prompt injection by email.

Questions

How do I give each customer their own agent inbox?
Provision an identity per tenant from code at signup, either as identities inside your workspace or as a workspace per tenant. Each gets its own key, mailbox, and grants.
Should tenants share one workspace?
Only if they can share an address book, since contacts are a workspace resource. Products that need contact isolation, separate billing, or tenant-owned domains should use a workspace per tenant.
What address should a new tenant get?
A subdomain of yours, immediately, so they are working without DNS. Offer their own domain as an upgrade, which also moves their sending reputation off yours.
How do I stop one tenant burning the shared allowance?
Cap per tenant in your own code before sending, low enough that your product refuses first with a clear message rather than the platform refusing with an opaque one.
How do I prove tenants are isolated?
Write the test: two tenants, one key, an attempt to read the other's mail, and assert the refusal. Isolation you have not tested is isolation you are hoping for.
What happens when a tenant leaves?
Delete the identity, its mail, and its grants within a defined window, and be able to say you did. If they own their workspace, they can also simply keep it.

Give your agent an address it can answer from.

Create an inbox