如何用AI代理自动化你的新闻通讯
Claude代理读取我的内容队列,选择本周最强的切入角度,用我的语气起草新闻通讯,按参与度层级分段列表,并通过Kit API安排发送——全程无需我打开编辑器。我查看渲染预览并点击批准。困难的创意工作是我的;机械执行是代理的。
每周三。28,400+ 读者。纯干货。
✓ 请查收邮箱 — 点击确认链接以完成订阅。
✓ 订阅成功!
✓ 您已在订阅列表中。
目录
2026年6月更新。
TL;DR: Claude代理读取我的内容队列,选择本周最强的切入角度,用我的语气起草新闻通讯,按参与度层级分段列表,并通过Kit API安排发送——全程无需我打开编辑器。我查看渲染预览并点击批准。困难的创意工作是我的;机械执行是代理的。
[运营者阅读] 一个持续发送的新闻通讯胜过那个”更好”但靠灵感驱动才发的。制约因素是执行开销,不是创意。我有想法;我没有每周格式化、安排和分段它们的带宽。代理消除了这个差距。
大多数新闻通讯工作流中真正的瓶颈
大多数新闻通讯自动化建议关注错误的事情:欢迎序列、自动化、标签逻辑。这些都很好,但解决不了每周的创建问题。
真正的阻碍是:你知道想说什么,但坐下来格式化、写主题行变体、选择合适的受众段、在正确时间安排发送,每周需要2-3小时的上下文切换。乘以52周,你已经花了整整一周只是在发送新闻通讯。
代理处理”我知道本周的角度是什么”之后的每个步骤。
我使用的技术栈
- Kit(前身为ConvertKit)——电子邮件平台。出色的API、稳固的订阅者标签、清晰的分析。代理友好的API是让我选择它的原因。
- Claude (Anthropic SDK)——生成层
- Cloudflare Workers——定时触发器(每周二上午8点CT运行)
- Airtable——内容队列和审批收件箱
如果你不在Kit上,相同的模式适用于任何有REST API可以创建和安排广播的平台。
第一步:内容队列
代理需要一个关于”我们在写什么”的真实来源。我的是一个Airtable表,包含以下列:
Topic——角度或问题Status——Queue / Approved / SentTier——是否面向所有订阅者或仅限高参与度订阅者Notes——任何约束(避免这种语气、包含这个链接等)
每周,我花10分钟在队列中添加2-3个主题。这是我的创意输入。其余的是代理的工作。
第二步:起草代理
// 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}`);
},
};第三步:审批步骤
代理在Kit的草稿状态下创建广播,并将Airtable记录标记为”Approved”。Kit向我发送带有预览链接的通知。我点击它,阅读,如果看起来没问题,我确认发送。如果我想要更改,直接在Kit中编辑。
这是防止代理在外发邮件上完全自主的关卡。我信任草稿大约90%的时间。审查中发现的10%——语气稍微不对、我想验证的统计数据、想添加的链接——值得那3分钟的审查。
代理处理的我再也不想做的事
- 编写主题行变体并选择最佳的
- 格式化预标题文本
- 计算正确的发送时间(我的受众在周四早上打开;代理知道这一点)
- 根据主题层级正确分段
- 将所有内容记录到Airtable以保留记录
我仍然拥有的
想法。队列中的主题是我的。角度是我的。代理是清晰简报的出色执行者;它不是战略层。如果我把一个糟糕的主题放入队列,我会得到一封关于糟糕主题的写得很好的新闻通讯。
还有:第一次审查关卡。每一次发送在发出之前都会经过我的眼睛。这不会改变。
运营者的底线
如果你每周花超过一个小时在新闻通讯机制上——格式化、安排、分段——你应该自动化它。Kit API很干净,Worker cron触发器稳如磐石,Claude草稿质量足够高,让我可以批准约90%的第一稿而不作修改。在Airtable中建立队列,连接Worker,然后回归创造想法而不是执行发送。
每周三。28,400+ 读者。纯干货。
✓ 请查收邮箱 — 点击确认链接以完成订阅。
✓ 订阅成功!
✓ 您已在订阅列表中。
将AI实战手册发送到您的邮箱
每周三。28,400+ 读者。纯干货。
请查收邮箱。
我们已向您发送确认邮件 — 点击其中的链接以完成订阅。如果一分钟内没收到,请检查垃圾邮件。
订阅成功。
欢迎 — 下一期很快就会送达您的邮箱。
您已在订阅列表中 — 每周三留意查收。