Generate-and-send demos beautifully.
It is also one crash away from a half-written campaign.
A client brought us in to fix an AI email workflow that had started drifting: the copy was changing tone between runs, small facts were sliding, the usual signs of a prompt nobody was governing. What the audit turned up was worse than the drift. Their pipeline generated the copy and handed it straight to the send API in one uninterrupted step, and a few days before I looked, a Claude session had died mid-run halfway through a re-engagement sequence, leaving three of five emails written and two that didn't exist yet. Under that design the crash wasn't supposed to matter, because by the time generation failed the half-finished work would already be on its way to real subscribers.
It didn't send that day, and the only reason was luck about where in the run the session happened to die. Leaning on that kind of luck isn't a system at all, just a coin toss nobody has noticed they're flipping. The instinct after a near miss like that is to make generation more reliable: better error handling, a retry, a longer timeout, a validation pass. All of that is the wrong fix. You can't make the model reliable enough to be trusted with the send button, because generation and delivery aren't the same job and they don't fail for the same reasons.
Generation and delivery fail differently
Generation fails because a session drops, a token limit cuts the output short, the model returns something malformed, or the draft is simply wrong in a way no schema catches. Delivery fails because an API key expired, a rate limit hit, the list reference went stale, or the send provider had an outage. Those are two different failure surfaces sitting on two different timelines. Generation is messy and slow, and it's supervised by a person who's watching the output appear. Delivery, by contrast, is fast and unattended and completely unforgiving, because on the other side of it are real inboxes and there's no recall.
When you couple them into one "generate and send" step, every failure in either half becomes a failure that can put bad mail in front of customers. A dropped session ships a half-written campaign, and a malformed or simply wrong draft ships in exactly the state the model left it. There's no point in the run where the work stops, sits still, and waits for a person to say yes. All of the risk comes from treating one brittle job and one irreversible job as a single uninterrupted action.
Decouple them with a pending queue
The fix is to separate the two into a queue with a human commit standing in the middle. Claude doesn't send anything. It writes a structured JSON brief to a pending/ directory and stops there. That file is a proposal, not a send. It sits in the queue until a person looks at it, and nothing downstream knows or cares whether the session that produced it is still alive.
The release decision is a git commit. A human reviews the file in pending/, and committing it is the APPLY signal, the exact same gate as typing APPLY to a governed prompt, except here it's version control doing the gating. The push triggers a CI workflow. CI reads the committed file, calls the send API, and on success moves the file from pending/ to sent/. Nothing reaches a subscriber until a person has committed the file that describes the send.
Here is the brief Claude writes to the queue. It's plain data, the full description of one send, with nothing executable in it.
{
"to": "list@client.com",
"from": "Brand <sends@client.com>",
"subject": "Morning brief, 27 May",
"text": "Full email body here...",
"digest_type": "daily-news"
}That structure is the entire interface between the two halves. Generation's only job is to produce a valid file like this and drop it in pending/. Delivery's only job is to read a committed file and call the API. Neither half knows or cares about the other's failures. Claude dying mid-session never kills a queued send, because the queue holds files, not live processes. The send API failing never forces a regeneration, because the brief is already written and committed and can simply be retried against the same file.
What the CI step actually does
The delivery half is deliberately small, because small is what makes it trustworthy. On every push, the workflow globs the pending/ directory for JSON files that were part of the commit, and for each one it does four things and nothing else: validate the file against the schema, call the send API with the exact payload in the file, check the response, and on a success code move the file to sent/ with the commit already attached to its history. On a non-success code it leaves the file in pending/ and fails the run loudly, so the send is retried on the next commit rather than silently lost.
The thing worth noticing is what's missing from that list. The CI step doesn't write copy, doesn't decide what goes out, and doesn't improvise if the file looks odd. It's a dumb, auditable executor of a decision a human already made by committing, and that's the point. You want the irreversible half of the pipeline to be the boring half, the one with no judgment in it, because judgment is exactly where things go wrong when nobody is watching.
The directories are the audit log
Because every send is a committed file that moves from one directory to another, the send history writes itself. A file in pending/ is a proposed send nobody has approved yet. Move it to sent/ and it becomes a send that happened, with a commit timestamp and an author attached to it. You don't build a separate logging system, because the queue is the log. You can read exactly what went out, when, who committed it, and what was sitting unapproved at any point, by reading the git history of two folders.
A bad draft has a safe failure mode under this design. It sits in pending/ until someone corrects it or deletes it. It can't leak out, because the only path to the send API runs through a commit, and nobody commits a draft they haven't read. The human gate stops being a polite intention you hope the operator remembers and becomes a structural fact of the pipeline. A pending file with no commit can't send. There's no override that skips the gate, because the gate is the mechanism, not a checkbox layered on top of it.
Where this is worth it, and where it is not
I want to be honest about the cost, because the pattern isn't free. You're trading immediacy for safety. A send that could have gone out the second the copy was written now waits for a person to review and commit, and that person has to be comfortable with git and with reading a CI log when a run fails. For a solo sender firing off one newsletter to their own list, that overhead isn't worth it, and I wouldn't set it up. The coin toss is fine when you're the only one who gets hurt by a bad flip.
The pattern earns its keep the moment an autonomous step can reach someone else's customers without a human in the loop: a client list, a scheduled digest, anything generated on a cron while nobody is watching the output appear. That's exactly where generate-and-send is most tempting, because it looks the most hands-off, and it's exactly where a half-written campaign is most expensive. Match the ceremony to the blast radius.
Email earns this, but it ports
Email is where this lesson is cheap to learn and expensive to ignore, because a bad send is public and permanent. There's no recall on a broadcast. The half-written sequence that almost shipped would have landed in thousands of inboxes with no way to pull it back, and the client would have paid for that in front of their own list, not in a log file nobody reads. But the pattern isn't really about email. Any autonomous AI action with a real-world consequence has the same shape of risk: a brittle generation step wired directly to an irreversible action. An agent that posts, pays, provisions, or messages a customer needs the same answer. Put a queue between the two, make the approval a committed artifact, and let the act of committing be the only path to execution. The medium changes from one workflow to the next, but the underlying structure carries over unchanged.
The uncomfortable part
Generate-and-send demos beautifully. In a recorded walkthrough it looks like the future: a prompt, a pause, an email in the inbox, no hands. That demo is exactly why the pattern is dangerous, because the thing that makes it impressive on stage is the thing that makes it untrustworthy in production. It removes the human from the most expensive decision in the workflow and calls that progress. The contrarian position is that only the async version, the one with a git commit standing between generation and delivery, is safe to point at a real client list. Treating the commit as the release decision feels slower and less magical than the demo. It's also the difference between a workflow you can safely hand back to a client and one that's one crashed session away from sending a campaign that was never finished.


