Skip to content

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

Last updated · 200 distilled / 1,178 read