Skip to content

PATTERN Cited by 1 source

Inbound-classify-persist-reply pipeline

Problem

An email agent has to do the same five things on every inbound message:

  1. Receive the raw MIME from an SMTP gateway.
  2. Parse it into a structured representation (headers, body, attachments, message-id).
  3. Classify what kind of message it is (ticket, invoice, alert, spam, follow-up, escalation, …).
  4. Persist the result into agent state and/or kick off async work.
  5. Reply or escalate — either immediately or after the async work completes (see concepts/asynchronous-reply-email).

Without a reusable pipeline, every team rebuilds the same five stages. "Rather than every team rebuilding the same inbound-classify-reply pipeline, start with this reference application" (Source: sources/2026-04-16-cloudflare-email-service-public-beta-ready-for-agents).

Pattern

Define the pipeline as five named stages inside a single Agent class's onEmail hook (plus, optionally, a Queue consumer or Workflow for step 5):

receive   → onEmail(email)       → Email Routing / Email Service
parse     → PostalMime.parse(raw)→ structured MIME
classify  → Workers AI / LLM     → {intent, urgency, …}
persist   → this.setState(...)   → DO-embedded state + Queue
reply     → this.sendEmail(...)  → Email Sending (sync or async)

Each stage is composable; each agent class picks its own classification logic, state schema, and reply template, but the stage-separation itself is reusable.

Canonical shape

From the Cloudflare 2026-04-16 Agents SDK worked example:

import { Agent, routeAgentEmail } from "agents";
import { createAddressBasedEmailResolver, type AgentEmail } from "agents/email";
import PostalMime from "postal-mime";

export class SupportAgent extends Agent {
  async onEmail(email: AgentEmail) {
    // 1. receive — framework delivers AgentEmail
    // 2. parse
    const raw = await email.getRaw();
    const parsed = await PostalMime.parse(raw);

    // 3. classify — optional; Agentic Inbox uses Workers AI here
    //    e.g. const { intent } = await this.env.AI.run("@cf/...", {...});

    // 4. persist
    this.setState({
      ...this.state,
      ticket: {
        from: email.from,
        subject: parsed.subject,
        body: parsed.text,
        messageId: parsed.messageId,
      },
    });

    // 4b. optional: kick off long-running background work
    // await this.env.QUEUE.send({ /* … */ });

    // 5. reply (can also emit later from a Queue handler /
    //    Workflow step — see [concepts/asynchronous-reply-email](<../concepts/asynchronous-reply-email.md>))
    await this.sendEmail({
      binding: this.env.EMAIL,
      fromName: "Support Agent",
      from: "support@your-agents.example",
      to: this.state.ticket.from,
      inReplyTo: this.state.ticket.messageId,  // signed reply-routing
      subject: `Re: ${this.state.ticket.subject}`,
      text: `Thanks for reaching out...`,
    });
  }
}

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

This is "the complete email agent pipeline that teams are building from scratch elsewhere: receive email, parse it, classify it, persist state, kick off async workflows, reply or escalate — all within a single Agent class, deployed globally on Cloudflare's network" (Source: sources/2026-04-16-cloudflare-email-service-public-beta-ready-for-agents).

Stage-by-stage choices

Receive

  • Email Routing on Cloudflare.
  • The email(message, env) handler on the Worker export default, delegated to routeAgentEmail with an address-based resolver.

Parse

  • postal-mime is the explicit choice in the Cloudflare worked example — pure-JS, WebAssembly-friendly, handles multipart / encoded / attachments.

Classify

  • Workers AI in the Agentic Inbox reference stack. Can also be any LLM via AI Gateway, or a classical classifier / regex / rules engine.
  • Classification is optional — simple agents can skip it.

Persist

  • DO-embedded state via this.setState(...) — survives the receive-work-reply latency gap; is the canonical agent memory substrate for email threads.
  • Optional Queue / Workflow dispatch for long-running work (see concepts/asynchronous-reply-email).
  • Attachments → R2 in Agentic Inbox.

Reply or escalate

  • Synchronously via this.sendEmail(...) inside onEmail.
  • Or later, from a Queue handler, Workflow step, scheduled task, or another Worker — replies route back to the same DO instance via signed inReplyTo headers.

Canonical wiki instances

  • Agentic Inbox (Cloudflare, 2026-04-16) — open-source reference app that implements the full pipeline end-to-end on Cloudflare primitives (Email Routing + Email Sending + Workers AI + R2 + Agents SDK).
  • Agents-SDK SupportAgent worked example (Cloudflare, 2026-04-16) — minimal in-post shape.

Extensions / composition

  • Add patterns/sub-addressed-agent-instance on the resolver side for per-ticket / per-conversation agent instances.
  • Add patterns/signed-reply-routing-header for reply-routing security.
  • Add a human-in-the-loop drafts tier (Agentic Inbox's built-in MCP server) for agent-drafted emails awaiting human Send.
  • Add follow-up scheduling via the scheduled-task surface so agents can nudge users on a timer.

When to apply

  • Any agent whose user-facing interface is email.
  • Any inbound-email automation (ticketing, invoicing, verification, notifications with structured replies).
  • Any agent whose work takes long enough that an async reply is natural (concepts/asynchronous-reply-email).

When not to apply

  • Fully synchronous chat-style agents — a chat UI is the natural fit; email forces unnecessary latency.
  • Fire-and-forget notifications — no reply expected; skip the pipeline and just send.
  • Bulk outbound marketing — different shape entirely (list management, bounce handling, unsubscribe, DKIM-alignment concerns at scale); transactional-email vendors are a better fit.

Seen in

Last updated · 200 distilled / 1,178 read