Alejandro Rioja.
AI Agents Growth

How to Automate Your Newsletter With an AI Agent

Alejandro Rioja
Alejandro Rioja
5 min read
TL;DR

A Claude agent reads my content queue, picks the strongest angle for the week, drafts a newsletter in my voice, segments the list by engagement tier, and schedules the send via the Kit API — all without me opening a composer. I review a rendered preview and hit approve. The hard creative work is mine; the mechanical execution is the agent's.

Free newsletter

Every Wednesday. 28,400+ operators. Zero fluff.

Table of contents

Open Table of contents

The actual bottleneck in most newsletter workflows

Most newsletter automation advice focuses on the wrong thing: welcome sequences, automations, tagging logic. Those are fine, but they don’t solve the week-to-week creation problem.

The real drag is this: you know what you want to say, but sitting down to format it, write the subject line variants, pick the right segment, and schedule it at the right time costs 2–3 hours of context-switching per week. Multiply by 52 weeks and you’ve spent a full work week just sending newsletters.

The agent handles every step after “I know what this week’s angle is.”

The stack I’m using

If you’re not on Kit, the same pattern works with any platform that has a REST API for creating and scheduling broadcasts.

Step 1: The content queue

The agent needs a source of truth for “what are we writing about.” Mine is an Airtable table with columns:

Each week, I spend 10 minutes adding 2–3 topics to the queue. That’s my creative input. The rest is the agent’s job.

Step 2: The draft agent

typescript
// workers/newsletter-agent/index.ts
import Anthropic from "@anthropic-ai/sdk";
import Airtable from "airtable";

const client = new Anthropic();

const VOICE_SYSTEM = `You are writing a weekly newsletter for Alejandro Rioja's subscribers.
His audience: founders and operators interested in AI agents, SEO, and growing a one-person business.
Voice: direct, first-person, practitioner. No hype, no "exciting times," no excessive bullet lists.
Structure every newsletter as:
1. One-sentence hook (the problem or observation)
2. The core insight (3–5 paragraphs, no headers, conversational)
3. One concrete action the reader can take this week
4. A short sign-off (2 sentences max)
Subject line: specific, outcome-oriented, under 50 chars. No clickbait.
Return JSON: { "subject": "...", "preheader": "...", "body": "..." }`;

async function getNextTopic(): Promise<{ id: string; topic: string; notes: string; tier: string }> {
  const base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY }).base(process.env.AIRTABLE_BASE_ID!);
  const records = await base("Newsletter Queue")
    .select({ filterByFormula: "{Status} = 'Queue'", sort: [{ field: "Created", direction: "asc" }], maxRecords: 1 })
    .firstPage();
  if (!records.length) throw new Error("Queue is empty. Add topics.");
  const r = records[0];
  return { id: r.id, topic: r.get("Topic") as string, notes: (r.get("Notes") as string) ?? "", tier: (r.get("Tier") as string) ?? "all" };
}

async function draftNewsletter(topic: string, notes: string): Promise<{ subject: string; preheader: string; body: string }> {
  const msg = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 2048,
    system: VOICE_SYSTEM,
    messages: [{ role: "user", content: `Write this week's newsletter on: "${topic}". Additional notes: ${notes || "none"}` }],
  });
  const text = (msg.content[0] as any).text.replace(/```json\n?/, "").replace(/```/, "").trim();
  return JSON.parse(text);
}

async function scheduleWithKit(draft: { subject: string; preheader: string; body: string }, tier: string): Promise<string> {
  const segmentId = tier === "engaged" ? process.env.KIT_ENGAGED_SEGMENT_ID : null;
  const sendAt = new Date();
  sendAt.setDate(sendAt.getDate() + ((4 - sendAt.getDay() + 7) % 7)); // next Thursday
  sendAt.setHours(9, 0, 0, 0); // 9am CT

  const payload: any = {
    broadcast: {
      subject: draft.subject,
      content: draft.body,
      description: draft.preheader,
      send_at: sendAt.toISOString(),
      email_layout_template: "minimal",
    },
  };
  if (segmentId) payload.broadcast.segment_id = segmentId;

  const res = await fetch("https://api.kit.com/v4/broadcasts", {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Kit-Api-Key": process.env.KIT_API_KEY! },
    body: JSON.stringify(payload),
  });
  const data = await res.json();
  return data.broadcast?.id ?? "";
}

export default {
  async scheduled(_event: ScheduledEvent, env: Env) {
    // Inject env vars
    Object.assign(process.env, env);
    const { id, topic, notes, tier } = await getNextTopic();
    const draft = await draftNewsletter(topic, notes);
    const broadcastId = await scheduleWithKit(draft, tier);

    // Mark as Approved in Airtable (not Sent — human reviews the Kit preview before confirm)
    const base = new Airtable({ apiKey: env.AIRTABLE_API_KEY }).base(env.AIRTABLE_BASE_ID);
    await base("Newsletter Queue").update(id, { Status: "Approved", KitBroadcastId: broadcastId });

    console.log(`Scheduled broadcast ${broadcastId} for topic: ${topic}`);
  },
};

Step 3: The approval step

The agent creates the broadcast in Kit’s draft state and marks the Airtable record as “Approved.” Kit sends me a notification with a preview link. I click it, read it, and if it looks right, I confirm the send. If I want changes, I edit directly in Kit.

This is the gate that keeps the agent from going fully autonomous on outbound email. I trust the drafts about 90% of the time. The 10% I catch in review — a tone that’s slightly off, a stat I want to verify, a link I want to add — is worth the 3-minute review.

What the agent handles that I never want to do again

What I still own

The idea. The topic in the queue is mine. The angle is mine. The agent is a great executor of a clear brief; it’s not a strategy layer. If I put a bad topic in the queue, I get a well-written newsletter about a bad topic.

Also: the first-review gate. Every single send gets my eyes on it before it goes out. That’s not going to change.

The operator’s bottom line

If you’re spending more than an hour a week on newsletter mechanics — formatting, scheduling, segmenting — you should automate it. The Kit API is clean, the Worker cron trigger is rock-solid, and the Claude draft quality is high enough that I approve ~90% of first drafts unchanged. Build the queue in Airtable, wire the Worker, and get back to creating ideas instead of executing sends.

Keep reading

Get the AI playbook in your inbox

Every Wednesday. 28,400+ operators. Zero fluff.

↵ to see all results esc esc to close