How a newsletter issue gets drafted
Once a week, the morning before the send day, an EventBridge Scheduler rule fires the composer Lambda. The Lambda reads the item pool, counts what’s fresh since the last issue, and decides whether there’s even enough to send. If there is, it calls a model once to write the whole issue — grounded only in those items and your voice doc — then checks its own work before handing the result on. The counting and the rules are plain Python. The writing is one Bedrock call, not a dozen.
Key takeaways
- The composer runs once a week via EventBridge Scheduler, the morning before the send day.
- It counts fresh items first — too few (default fewer than three) means skip, not pad.
- Four moves per run: skip, draft, redraft, ready.
- The draft is grounded only in the gathered items and the voice doc — every claim links to a source.
- A self-check catches any unsourced line and triggers one redraft before the issue is marked ready.
The decision flow, per run
Three items isn’t magic, it’s in the doc
The rules doc has one short line for the threshold: “Send an issue when at least three fresh items are in the pool. Below that, skip and tell me why.” The number is how many genuinely new items have to be waiting before an issue is worth your readers’ time. Set it to three and a slow week skips. Set it to one and you’ll send every week, thin issues included. Set it to five and you’ll only send when there’s real news. The number is yours to change without touching code.
The threshold exists for one reason: a thin issue is worse than no issue. The fastest way to train your list to stop opening your email is to send them a newsletter with one stretched-thin update and a lot of filler. Skipping a quiet week respectfully protects the weeks when you do have something to say.
Grounded drafting: the model writes only from your items
When there’s enough to send, the composer makes one Bedrock call. The prompt has three parts: your voice doc (tone, greeting, sign-off, examples, the never-say list), the fresh items as a clean list (each with its title, note, category, and link), and a short instruction: “Write this week’s issue using only these items. One short paragraph per item, in this order. Use the voice. Put the link in. Do not add news that isn’t in the items. Tag each paragraph with the id of the item it came from.”
That last instruction is the important one. Because every paragraph is tagged with the item it came from, the composer can check — and the owner can see — exactly where each line came from. The model isn’t asked to be clever or to find news; it’s asked to turn a list you approved into a friendly email in your voice. The judgment about what goes in the issue was already made when the items were gathered and approved in Part 2.
The heavier reasoning model (Sonnet 4.6) is used here, and only here, because writing a coherent issue across several items in a consistent voice is the one genuinely hard writing task in the whole system. Everywhere else — tidying a forwarded note in Part 2, the monthly summary in Part 6 — the cheaper Haiku 4.5 is plenty.
Four moves, always
Every weekly run lands in exactly one of four buckets. The names are simple on purpose.
- Skip. Fewer fresh items than the threshold. Send nothing this week. Write a short note to the owner: “Only two new items this week, skipping. Here they are if you disagree.” Log the skip so the trail shows why no issue went out.
- Draft. Enough items. Make the one Bedrock call and produce the first full issue, grounded in the items and the voice.
- Redraft. The self-check found a sentence that doesn’t trace back to any item. Make one more call asking the model to either ground the line in a real item or drop it. One redraft, not a loop — if the second pass still has a gap, the issue is handed over with the gap flagged for the owner rather than rewritten forever.
- Ready. Every line traces to a source. Mark the draft ready and hand it to the sender for the owner’s review (Part 4). Most weeks with enough items land here after one draft and a clean check.
State that keeps the run honest
The composer reads and writes a small DynamoDB table, nc-items-state, that records which items have already been used in a past issue: (item_id, used_in_issue, used_date). That’s how “fresh” is defined — an item that’s already gone out in a previous issue isn’t counted again. A second table, nc-issues, records each run: the move chosen, the item ids used, and a pointer to the draft stored in S3. With those two tables, the count is deterministic and a re-run produces no duplicate issue: the state shows what already happened.
Approving an issue (Part 5) marks its items as used. A skipped week leaves the items fresh, so they roll into next week’s count naturally.
Why the model only writes, never decides to send
The composer could ask a model whether the week is worth sending, or let it pick which items make the cut. It doesn’t. The decision to send and the choice of items are deterministic and human-controlled — the threshold is a number you set, and the items were approved one by one in Part 2. The model’s only job is to turn an approved list into a well-written email. Keeping the model out of the “should we send?” decision means the system never surprises you by mailing your list on a week you’d have sat out, and never quietly drops an item you wanted in.
Next post: how the finished draft reaches the owner — reviewer resolution, the grounding flag, quiet hours, and the four guardrails between the draft and the review message landing.
All posts