Part 2 of 7 · Newsletter composer series ~4 min read

How a newsletter gathers the week’s updates

The composer can only write about what it’s been given. So the first job is making sure the item pool actually reflects what happened this week. There are three ways an item gets in: somebody types a row in the Drive sheet, your blog publishes a post the feed lane picks up, or somebody forwards an update to a dedicated address. The first one is obvious. The other two exist because in real life nobody types a row in a sheet for the post that went live three minutes ago.

Key takeaways

  • Three intake lanes feed one item pool: the Drive sheet, a blog feed lane, and an inbox-forwarding lane.
  • The feed lane watches your blog’s RSS and proposes a row for each new post.
  • Forwarded updates are tidied by Bedrock Haiku 4.5 into a clean title and a one-line note.
  • Every proposed row goes to the team’s Slack for one-tap approval before it lands in the pool.
  • The Drive sheet stays the canonical store. The other lanes are conveniences that write into it.

Three lanes into one item pool

Three intake lanes funnel into one item pool A diagram with three vertical lane columns at the top and a single unified row at the bottom. Lane one, Drive sheet: somebody types a row directly into the Google Sheet that holds the item pool; the drive-sync Lambda mirrors the sheet to S3 every 15 minutes, and the composer reads from there. Lane two, Blog feed: a feed-sync Lambda checks your blog's RSS feed every hour; for each new post it hasn't seen before, it pulls the title, link, and summary and creates a proposed row; the proposal is posted to the team's Slack with Approve and Edit buttons; on approve, the row is added to the Drive sheet via the Sheets API. Lane three, Inbox forwarding: somebody forwards an update to a dedicated address, updates-at-your-company; SES writes the raw message to S3; a parser Lambda reads the body and calls Bedrock Haiku 4.5 to tidy it into a clean title, a one-line note, and a category; the same proposal-and-approval flow runs in Slack before the row lands in the sheet. All three lanes converge on the same Drive sheet, which the drive-sync Lambda keeps mirrored to S3 for the composer to read. A note at the bottom: the Drive sheet stays the source of truth — the other lanes are conveniences that propose rows for it. Lane 1 · manual Drive sheet • Somebody types a win or note • drive-sync mirrors to S3 every 15 min • Composer reads from S3 • Source of truth stays in Drive Lane 2 · RSS poll Blog feed • feed-sync checks your RSS hourly • New post → pull title, link, summary • Proposes a row for the post • Slack one-tap approve → sheet Lane 3 · SES + Haiku Inbox forwarding • Forward update to updates-address • SES writes body to S3 • Haiku 4.5 tidies into title + note • Same proposal flow to Slack as Lane 2 Drive item pool sheet (source of truth) title · note · category · link · date · added-by · used-in-issue drive-sync mirrors it to S3 every 15 min — composer reads from S3 to composer, weekly run The Drive sheet stays the source of truth — the other lanes are conveniences that propose rows for it.
Fig 2. Three lanes converge on one Drive sheet. The sheet is the source of truth; the feed lane and the inbox lane are conveniences that propose rows for human approval. The drive-sync Lambda mirrors the sheet to S3 so the composer can read it without hitting Drive on every run.

Lane 1: the Drive sheet itself

The simplest lane. Open the item-pool sheet in Drive, add a row, save. The columns are short: a title, a one-line note, a category (blog post, product note, win, event, other), a link, the date it happened, and who added it. A small Lambda — drive-sync — runs every fifteen minutes, exports the sheet as plain CSV via the Drive API, and writes it to s3://nc-items-source/items.csv if the sheet has changed since the last sync. The composer reads from S3, not Drive directly. That keeps Drive API calls predictable and gives you S3 versioning for free, so a bad bulk-edit can be rolled back in one click.

This lane covers the things that don’t show up anywhere else: the client win, the trade-show booth that went well, the kind note a customer sent. Anyone on the team can drop those in throughout the week without learning a new tool.

Lane 2: the blog feed (the lane that fills itself)

Most of what belongs in a newsletter is already published somewhere — on your blog. A small feed-sync Lambda runs hourly, fetches your blog’s RSS or Atom feed, and compares the entries against the ones it has already seen (tracked by a stable id per entry in DynamoDB). For each genuinely new post, it pulls the title, the link, and the short summary the feed provides, and creates a proposed row. The proposal goes to the team’s Slack with approve, edit, and discard buttons. On approve, the row is written to the Drive sheet via the Sheets API.

The reason a new post is a proposal and not an automatic add is simple: not every post belongs in every issue. A small internal note, a re-publish, or a post that’s off-topic for the list shouldn’t pad the issue. One tap keeps the good ones and drops the rest, and the team stays in the loop on what the newsletter will cover.

Lane 3: inbox forwarding

Set up a dedicated inbound address — something like updates@your-company.com — via Amazon SES. Anyone on the team forwards an update to that address: a Slack message they copied, a quick “we shipped X” note, a screenshot caption, a paragraph about a customer story. SES writes the raw message to s3://nc-raw-mime/. The S3 PUT triggers a parser Lambda. The Lambda reads the body and calls Bedrock Haiku 4.5 to tidy the raw text into a clean title, a one-line note, a category, and any link it found. The model prompt is short: “Turn this forwarded update into one newsletter item. Return JSON only. Keep the facts; do not add any that aren’t in the text.”

The tidied item goes to the same Slack proposal flow as Lane 2 — the proposed row, and three buttons: approve, edit, discard. On approve, a Lambda writes the row to the Drive sheet. On edit, the person gets a fillable modal pre-populated with the proposal. On discard, the message is logged and the raw forward moved to a discarded prefix in S3 for audit. Forwarding is the lane that catches the wins that live in someone’s head or a chat thread and would otherwise never reach the list.

Why the pool stays the source of truth

Three lanes in, but only one place the composer actually reads. That’s a deliberate constraint. If the feed lane and the inbox lane both wrote straight into the draft, every “why is this in the issue?” question would mean checking three places. Funneling everything through the Drive sheet means there is exactly one row per item, anyone can read or edit any of it, and the composer’s input is a single, reviewable list. The convenience lanes are first-class for getting items in, but they always pass through the sheet on the way.

Next post: how the composer reads the pool, decides whether there’s enough to send, and drafts the issue grounded only in those items.

All posts