Alejandro Rioja.
AI Agents Growth

AIエージェントでニュースレターを自動化する方法

Alejandro Rioja
Alejandro Rioja
2 分で読める
TL;DR

Claudeエージェントがコンテンツキューを読み取り、その週の最も強い切り口を選び、私の声でニュースレターを下書きし、エンゲージメント層別にリストをセグメント化し、Kit API経由で送信をスケジュールします——私がエディタを開かずに。レンダリングされたプレビューを確認して承認ボタンを押すだけです。難しいクリエイティブな作業は私のもの。機械的な実行はエージェントのものです。

無料ニュースレター

毎週水曜。28,400人以上の読者。無駄なし。

目次

2026年6月更新。

TL;DR: Claudeエージェントがコンテンツキューを読み取り、その週の最も強い切り口を選び、私の声でニュースレターを下書きし、エンゲージメント層別にリストをセグメント化し、Kit API経由で送信をスケジュールします——私がエディタを開かずに。レンダリングされたプレビューを確認して承認ボタンを押すだけです。難しいクリエイティブな作業は私のもの。機械的な実行はエージェントのものです。

[オペレーターの視点] 一貫して送信されるニュースレターは、「より良い」が霊感が湧いたときだけ発行されるものに勝ります。制約はアイデアではなく、実行のオーバーヘッドでした。アイデアはあった。毎週それをフォーマット、スケジュール、セグメント化する帯域幅がなかっただけです。エージェントがそのギャップを埋めました。

ほとんどのニュースレターワークフローにおける本当のボトルネック

ほとんどのニュースレター自動化のアドバイスは間違ったことに焦点を当てています:ウェルカムシーケンス、オートメーション、タギングロジック。それらは問題ありませんが、毎週のコンテンツ作成問題を解決しません。

本当の妨げはこれです:言いたいことはわかっている。しかし座ってフォーマットし、件名のバリエーションを書き、適切なセグメントを選び、適切なタイミングでスケジュールすることが週に2〜3時間のコンテキストスイッチングを要します。52週で掛けると、ニュースレターを送信するだけに丸々1週間を費やしたことになります。

エージェントは「今週の切り口がわかった」の後のすべてのステップを処理します。

使用しているスタック

Kitを使っていなくても、ブロードキャストを作成・スケジュールするREST APIを持つプラットフォームなら同じパターンが機能します。

ステップ1:コンテンツキュー

エージェントには「何について書くか」の信頼できる情報源が必要です。私のは以下の列を持つAirtableテーブルです:

毎週、キューに2〜3個のトピックを追加するのに10分を費やします。それが私のクリエイティブな入力です。残りはエージェントの仕事です。

ステップ2:下書きエージェント

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}`);
  },
};

ステップ3:承認ステップ

エージェントはKitの下書き状態でブロードキャストを作成し、Airtableレコードを「Approved」としてマークします。Kitがプレビューリンク付きの通知を送ってきます。クリックして読み、問題なければ送信を確認します。変更が必要な場合はKitで直接編集します。

これがエージェントが送信メールで完全に自律的になるのを防ぐゲートです。下書きは約90%の確率で信頼しています。レビューで見つける10%——わずかにずれたトーン、確認したい統計、追加したいリンク——は3分間のレビューの価値があります。

エージェントが処理する、二度とやりたくないこと

私がまだ所有していること

アイデア。キューのトピックは私のもの。切り口は私のもの。エージェントは明確なブリーフの優れた実行者です。戦略レイヤーではありません。キューに悪いトピックを入れれば、悪いトピックについてうまく書かれたニュースレターが得られます。

また:最初のレビューゲート。すべての送信は配信前に私の目を通します。これは変わりません。

オペレーターの結論

ニュースレターの機械的作業——フォーマット、スケジューリング、セグメンテーション——に週1時間以上費やしているなら、自動化すべきです。Kit APIはクリーンで、WorkerのCronトリガーは岩のように堅固で、Claudeの下書き品質は私が第一稿の約90%を変更なしで承認できるほど高いです。Airtableにキューを構築し、Workerを接続して、送信を実行する代わりにアイデアを作ることに戻りましょう。

続きを読む

AIプレイブックをメールでお届け

毎週水曜。28,400人以上の読者。無駄なし。

↵ すべての結果を見る esc esc で閉じる