PATTERN Cited by 1 source
Signed reply-routing header¶
Problem¶
When an agent sends an email and expects a reply, the inbound side needs to route that reply back to the exact agent instance that sent the original message — even across plus-sub-addressing changes, even across forwarded or bounced-and-resent chains, even after delays of hours or days.
Naïvely, the reply-routing decision is a function of untrusted
headers — the To: address, In-Reply-To: / References: headers,
Message-ID: values. Any of these can be:
- Forged by an attacker trying to route their email into a victim's agent instance.
- Mangled by intermediate mail servers that rewrite plus-sub segments or normalise Message-IDs.
- Replayed in a fresh message long after the original conversation should be over.
"A security concern that most 'email for agents' solutions haven't addressed" (Source: sources/2026-04-16-cloudflare-email-service-public-beta-ready-for-agents).
Pattern¶
Sign the reply-routing headers with HMAC-SHA256 on outbound using a server-side secret, and verify the signature on inbound before dispatching the reply to its destination DO instance.
"When your agent sends an email and expects a reply, you can sign the routing headers with HMAC-SHA256 so that replies route back to the exact agent instance that sent the original message. This prevents attackers from forging headers to route emails to arbitrary agent instances" (Source: sources/2026-04-16-cloudflare-email-service-public-beta-ready-for-agents).
Canonical shape¶
// Outbound — agent replies and sets a signed inReplyTo
await this.sendEmail({
binding: this.env.EMAIL,
from: "support@your-agents.example",
to: this.state.ticket.from,
inReplyTo: this.state.ticket.messageId, // ← Cloudflare layer
// signs this with
// HMAC-SHA256
subject: `Re: ${this.state.ticket.subject}`,
text: `...`,
});
┌─────────────────────────┐
│ Agent DO instance A │
│ sendEmail({ inReplyTo })
└───────────┬─────────────┘
│
Cloudflare signs inReplyTo
with HMAC-SHA256 + server key
including DO-instance-ID + ts
│
▼
SMTP wire
│
┌───────┴───────┐
▼ ▼
User inbox Forwarder / relay
│ │
Reply │
▼ ▼
Inbound SMTP ←──────┘
│
Cloudflare verifies HMAC signature
in inReplyTo / Message-ID
│
├── valid + instance-ID = A → route to DO instance A
├── valid + different instance-ID → route to that one
├── invalid signature → fallback (address-based resolver,
│ drop, or quarantine)
└── missing signature → fallback (address-based resolver)
What the signed payload carries¶
The published post describes the intent, not the exact wire format. Typical contents an implementation must include:
- Agent class name + DO instance ID — so the verifier can rebuild the destination.
- Timestamp — to detect replay after some expiry window.
- Nonce / random salt — to prevent pre-computed forgeries for a given instance.
- MAC over the canonical string, with a server-side HMAC key (rotated on a schedule).
Interaction with address-based routing¶
Address-based routing is the fallback shape (for fresh inbound mail with no prior thread); signed reply routing is the thread-continuation shape:
| Inbound mail | Routing path |
|---|---|
| Fresh (no signed header) | Address-based resolver (support+ticket-123@… → DO instance) |
| Reply with valid signed header | Signed header → DO instance (source of truth) |
| Reply with invalid / tampered signed header | Drop / quarantine — do NOT fall back to the unsigned address |
| Reply with missing header (intermediary stripped) | Fall back to address-based — best-effort |
The "do not fall back to unsigned address on tamper-detection" rule is load-bearing: a silent fallback is an attack vector (strip the header to downgrade to unauthenticated routing).
When to apply¶
- Any agent that accepts user replies in an email thread.
- Any agent whose reply-routing decision implies state mutation (ticket close, order change, password reset).
- Any agent where the address-based resolver alone can't distinguish between legitimate replies and forged "you've been escalated" mails claiming to be replies to an earlier agent conversation.
When not to apply / weaker-substitute trade-offs¶
- Pure inbound-only agents — if the agent never expects a reply (one-shot notification), the reply path doesn't need protection.
- Non-stateful agents — if every inbound mail is routed fresh by address alone and nothing about prior state is used, signed reply routing buys less.
Seen in¶
- sources/2026-04-16-cloudflare-email-service-public-beta-ready-for-agents
— canonical wiki instance; Cloudflare's Agents-SDK email-send API
layer-signs
inReplyToautomatically when requested.
Related¶
- concepts/address-based-agent-routing — fresh-inbound counterpart.
- concepts/email-as-agent-interface — channel context.
- patterns/sub-addressed-agent-instance — address-level instance addressing this pattern hardens.
- patterns/inbound-classify-persist-reply-pipeline — the pipeline this signing layer lives inside.
- systems/cloudflare-email-service / systems/cloudflare-agents-sdk / systems/cloudflare-durable-objects — product + substrate.