DEV Community

Cover image for Pausing the one Amazon SES lane that's bouncing, and proving the pause took
Taranity
Taranity

Posted on

Pausing the one Amazon SES lane that's bouncing, and proving the pause took

A small shop usually sends all its mail through one Amazon SES account: receipts, password resets, the newsletter. SES judges bounces across the whole account, so one stale list imported into the newsletter can take the password resets down with it.

I built Fothering for the AWS CDS Agentic AI Partner Hackathon to stop that. The name comes from seamanship. Fothering a ship means drawing a sail under the hull over a leak so she can keep going, and it's how HMS Endeavour got off the Great Barrier Reef in June 1770.

## What it does

Each kind of mail goes through its own SES configuration set, and SES publishes each set's events to EventBridge. Fothering keeps a minimized copy of every event (the recipient's domain and a keyed hash, never the address, expiring after 30 days) and looks at every lane every five minutes. A lane opens an incident at 5% permanent bounces or 0.1% complaints, over at least 100 sends and 3 events.

It then pauses that one set with PutConfigurationSetSendingOptions, reads the setting back with GetConfigurationSet, checks about ten minutes later that nothing got through, and emails the operator the evidence and the command to reopen. It never pauses a transactional lane or reopens anything by itself.

## Where the agent sits

The agent runs on Amazon Bedrock AgentCore Runtime in eu-central-1 and calls Claude Sonnet 4.6 through Bedrock's EU inference profile. It gets one evidence record and the lane that code says may be closed. It confirms that lane or answers unknown, and it writes one paragraph for the email, which the email labels as the agent's. It holds no tool that changes anything. A separate function checks the decision again before it touches SES, and an unknown closes nothing.

I'd rather the guard do nothing than guess. Pausing the wrong lane is the exact failure it exists to prevent.

## The live run

On 22 October I deployed the four stacks into a real AWS account and sent the scenario to the SES mailbox simulator. On the bulk lane 20 of 100 recipient-sends bounced for good, and all 20 came from the batch stale-import. SES answered SendingEnabled false for fothering-bulk. A test send on the paused set was refused with SendingPausedException, while the transactional set kept accepting mail. I reopened it from the email's command and removed every stack.

## Things I learned

The first live run closed the lane and then failed to send its own email, with AccessDeniedException. In the SES sandbox, ses:SendEmail is also authorized against the recipient's identity, not only the sender's. My role named the From domain and the From address, never the operator's address. After granting the send on that identity too, the re-run sent the email on its first attempt.

Bedrock refused Claude Sonnet 5 on this account with "not available for this account", although the use case form was on file. Access to a Marketplace model has four parts (region, entitlement, authorization and agreement), and aws bedrock get-foundation-model-availability shows each one. The agent moved to Sonnet 4.6.

In the first live run the model's whole part was one call, 2,276 tokens in and 245 out.

A ratio needs a minimum count beside it. At 100 sends a single complaint already reads 1%, ten times the complaint threshold, and one report is not evidence that a stream went wrong. So a lane needs 3 events before its ratio counts.

DynamoDB's TTL deletes an expired item "typically within a few days", and until then it still shows up in reads. Every query filters on expiry, and the privacy page says records are deleted within a few days after 30 days, not on day 30.

## What it isn't yet

It has only run against the mailbox simulator, and only one scenario ran live, so I'm not putting a performance number on it until there are three live runs. AWS also documents its own per-set pause, a CloudWatch alarm that notifies SNS and triggers a Lambda function. Fothering adds the named cause, the read-back, and a refusal to close anything when the cause isn't settled.

## Links

Top comments (0)