Kreado de Mia Propra Bultenletero kun Claude

Dua tago de la Hacker News trafika pinto. Kvardek mil vizitantoj, neniu maniero kontakti ilin denove. Mi bezonis bultenletero-abonformulon.

Mi ekzamenis Buttondown, Beehiiv, Substack, ConvertKit. Ĉiuj tro abundaj. Mi nur bezonis kolekti retpoŝtojn. Mi ne bezonis kampanjojn, analizojn aŭ abonantaran administradon. Kaj mi volis posedi miajn datenojn.

Do mi petis al Claude konstrui ĝin.

flowchart LR
    User([User]) --> Form[Newsletter Form]
    Form -->|POST /api/subscribe| Worker[Cloudflare Worker]
    Worker --> KV[(Cloudflare KV)]
    KV -.->|Daily sync| GHA[GitHub Actions]
    GHA -.-> Repo[(subscribers.jsonl)]
flowchart TB
    User([User]) --> Form[Newsletter Form]
    Form -->|POST /api/subscribe| Worker[Cloudflare Worker]
    Worker --> KV[(Cloudflare KV)]
    KV -.->|Daily sync| GHA[GitHub Actions]
    GHA -.-> Repo[(subscribers.jsonl)]

La Agordo

Mi donis al Claude Cloudflare API-ĵetonon per medio-variablo kaj priskribis kion mi volis: formularon kiu kolektas retpoŝtojn kaj konservas ilin ie kie mi regas.

Malpli ol 30 minutojn poste, mi havis funkcianta bultenleteron.

Kion Claude Konstruis

KomponentoTeknologio
FormularoHTML + vanilla JS
MalantaŭoCloudflare Worker
KonservejoCloudflare KV
SinkronigoGitHub Actions

La formularo estas Hugo partial kiu postas al /api/subscribe:

<form id="newsletter-form">
  <input type="email" name="email" placeholder="[email protected]" required>
  <button type="submit">Subscribe</button>
</form>

La malantaŭo estas 42-linia Cloudflare Worker kiu traktas validigon, normaligon, duoblaĵ-detekton kaj referer-sanigon:

export async function onRequestPost(context) {
  const { email } = await request.json();

  // Validate, normalize, check for duplicates
  const emailKey = email.trim().toLowerCase();
  const existing = await env.SUBSCRIBERS.get(emailKey);
  if (existing) {
    return Response.json({ success: true, message: "Already subscribed" });
  }

  // Sanitize referer (strip query params for privacy)
  const referer = request.headers.get("Referer");
  const source = referer ? new URL(referer).origin + new URL(referer).pathname : "direct";

  // Store in KV with metadata
  await env.SUBSCRIBERS.put(emailKey, JSON.stringify({
    subscribedAt: new Date().toISOString(),
    source
  }));

  return Response.json({ success: true }, { status: 201 });
}

Rapidlimigo venas de la enkonstruita protekto de Cloudflare—neniu ekstra kodo bezonata.

GitHub Actions laborfluo ruliĝas ĉiutage por sinkronigi abonantojn de Cloudflare KV al JSONL-dosiero en la deponejo. Mia abonanta listo vivas en versia kontrolo. Se Cloudflare fermos mian konton morgaŭ, mi ankoraŭ havos miajn datenojn. Kaj ĝi estas en formato kun kiu LLM-oj povas facile labori.

La Laborfluo

Claude iteraciis loke ĝis ĉio funkciis—formulara sendo, KV-konservejo, eraroj-traktado. Poste puŝis al antaŭvida aplikaĵo, testis la kompletan fluon kaj kunfandis al main.

La formularo eklive ekde kiam la trafiko ankoraŭ fluis.

Ĉi tio tuta estis konstruita en unu sesio.

Kial Ne SaaS?

Buttondown estas bonega se vi bezonas ĝin. Mi ne. Miaj postuloj:

  1. Kolekti retpoŝtojn
  2. Konservi ilin ie kie mi regas
  3. Liveri rapide

Jen ĉio. Mi ne bezonas gutumadajn kampanjojn aŭ A/B-testojn aŭ ŝikajn ŝablonojn. Mi bezonas liston de retpoŝtaj adresoj kiujn mi posedas.

42 linioj de kodo anstataŭ alia monata abono. Foje la simpla solvo estas la ĝusta.

La konkludo: neperfekta kaj viva venkas perfektan kaj planitan. Mi povus pasigi horojn taksante bultenletero-servojn, komparante funkciojn, legante recenzojn. Anstataŭe mi liveris ion en 30 minutoj kaj daŭrigis.