Outbound Mail Agent

Homebrewed

After inbound, Processing Q and Thank-you Q run in parallel — process accepted mail (today SES forward) and send the category-framed reply — each with its own retries and DLQ.

Amazon SQS · Lambda · SES · S3 · DynamoDB · OpenAI gpt-4o-mini / Typesafe Jev · CloudWatch · Terraform

Overview

Layer 4 Act on the Mental Model. Outbound Mail runs after inbound accepts clean contact mail: parallel Processing Q and Thank-you Q workers (no SES send on inbound).

Behavior

  • Parallelization (Anthropic). Inbound enqueues Processing Q and Thank-you Q together; the two paths run independently. See Building effective agents.
  • Independent outcomes. Process (today: forward) and thank-you do not wait on each other. Each records its own DynamoDB fields (forward_ses_id, thank_you_ses_id / thank_you_status).
  • Per-queue idempotency. Retries skip work already done on that queue so hotmail is not double-forwarded and the sender is not double-thanked.
  • Hard send failures retry. SES or unexpected errors raise so that queue redelivers (up to 3 receives), then that queue’s DLQ. Soft thank-you compose failures still attempt a template send.
  • Shared raw MIME. Both workers reload ses-raw/<messageId> from S3; they do not depend on the inbound Lambda staying warm.

Send flow

Inbound fans out to both queues (Parallelization); either path can finish first. Diagram key: ✦ AI agent · second line = Anthropic workflow · purple = actor · white = service.

---
config:
  theme: default
  themeVariables:
    background: "#ffffff"
    fontSize: "14px"
---
flowchart TB
    inbound["✦ Inbound Mail agent
Routing"] procQ[Processing Q] thanksQ[Thank-you Q] procAgent["Processing worker
SES forward"] thanksAgent["✦ Thank-you Mail
Parallelization"] actionNode["SES forward
FORWARD_TO"] composeNode["✦ Compose thank-you
Parallelization"] thanksNode["SES thank-you
to sender"] procDone[Process recorded] thanksDone[Thank-you recorded] procDlq["Processing DLQ
after 3 receives"] thanksDlq["Thank-you DLQ
after 3 receives"] alarm[CloudWatch alarm] inbound --> procQ inbound --> thanksQ procQ --> procAgent --> actionNode --> procDone thanksQ --> thanksAgent --> composeNode --> thanksNode --> thanksDone procAgent -->|hard failure| procQ procQ -->|max receives| procDlq --> alarm thanksAgent -->|hard SES failure| thanksQ thanksQ -->|max receives| thanksDlq --> alarm classDef agent fill:#e8f0ee,stroke:#0f3d36,stroke-width:2px,color:#0d0d0d classDef service fill:#fafaf8,stroke:#c8c8c2,stroke-width:1px,color:#0d0d0d classDef actor fill:#f3eef8,stroke:#6b4f9a,stroke-width:2px,color:#0d0d0d class inbound,thanksAgent,composeNode agent class procQ,thanksQ,procAgent,actionNode,thanksNode,procDone,thanksDone,procDlq,thanksDlq,alarm service

After the Inbound Mail agent accepts a message, it enqueues Processing Q and Thank-you Q together. Processing takes the appropriate action for the job (currently SES forward to FORWARD_TO); Thank-you drafts and sends the reply to the original sender. The two do not wait on each other. DynamoDB records each step so queue retries do not double-send.

This agent is part of the OPG Labs project (Project tab).