Let an agent receive email
Do this when an agent should act on mail somebody sends: a bug report, an alert, a customer question. Two shapes, and they meet in the same place. An address of its own. Mail addressed to the agent is delivered by an inbound-parse provider, which POSTs the parsed message to a webhook. A mailbox you already have. AgentArea polls it over IMAP, read-only, and reacts to what arrives. Either way the message becomes a task, and the agent’s reply goes back into the same mail thread — recipients see a conversation, not a stream of unrelated notifications.What this platform does and does not run
AgentArea does not accept SMTP. There is no MX record, no mail server, and no address namespace in this repository. What it has is everything after a message exists: parsing, routing to an agent, threading, and the reply. That line is deliberate. Handing outsomething@your-domain means running a
domain’s deliverability, quotas, and abuse handling, which only makes sense for
whoever operates that domain. AgentArea’s hosted offering allocates addresses
that way; a self-hosted install points an inbound-parse provider (or its own
mail server) at the webhook below and owns its own namespace. See
Open core for how that seam is wired.
Give an agent an address
1. Create the trigger
webhook_type: "email" tells the webhook to read the body as a message. The
field_map says which JSON keys your provider uses — every provider names them
differently, so this is configuration rather than a per-vendor integration.
webhook_id. The endpoint is
POST /v1/webhooks/{webhook_id}.
A path segment is not a secret. Set validation_rules.signing_secret to the
secret your provider signs with, and the request is rejected unless the HMAC
over the raw bytes matches. Providers disagree on the header and encoding, so
signature_header, signature_algorithm and signature_prefix are
configurable alongside it:
channel_credentials are the SMTP settings for the reply, stored encrypted
under channel_cred:email:{trigger_id}. Without them the agent can read mail
but not answer.
2. Point the address at the webhook
In your provider, route the agent’s address to that URL. The URL is what identifies the agent — one address, one trigger, no ambiguity about who the mail was for. Field paths support nesting (FromFull.Email) and lists
(ToFull[].Email, which keeps every recipient in to_all and uses the first as
to).
Connect a mailbox you already have
Polling is a cron trigger with theimap extractor. The credential goes to the
secret store, never into the trigger’s config column.
- Your mail is not touched. The mailbox is opened read-only and progress is tracked by UID, so nothing is marked as read behind your back.
- History is not replayed. The first poll records where the mailbox is and emits nothing. Only mail arriving afterwards reaches the agent.
How the reply threads
Each inbound message carries a thread key — the root of itsReferences
header, falling back to In-Reply-To, then to its own Message-ID. Every
message in one conversation therefore resolves to the same key.
That key is the conversation identity. When mail arrives for a thread whose task
is still running, it is routed into that task rather than starting a new one, so
the agent answers with the conversation in hand. The outgoing reply carries
In-Reply-To and the full References chain, which is what makes mail clients
show it as a reply.
Try it locally
The dev stack bundles mailpit, which can POST every new message to a webhook. No provider account, no public DNS:mailpit:1025 from inside the compose network and watch the
task appear. Note that mailpit’s webhook carries a snippet, not the full
body — it is a development mail catcher, and real inbound-parse providers post
the whole message.
Limits
- One message per poll. A trigger execution becomes one task with one reply
address, so an IMAP poll emits the oldest unread message and reports the rest
as
pendingin its state; they drain on following ticks. Match the cron interval to the volume you expect. webhook_type: "gmail"is a different thing. Gmail’s Pub/Sub push notifies you that something changed and carries no message, so an agent cannot read the mail from it. Useimapagainst Gmail instead.- Attachments are not extracted. The message text reaches the agent; attachments stay in the raw payload.