Creando Mi Propio Boletín con Claude

Segundo día del pico de tráfico de Hacker News. Cuarenta mil visitantes, sin forma de contactarlos nuevamente. Necesitaba un formulario de suscripción para el boletín.

Revisé Buttondown, Beehiiv, Substack, ConvertKit. Todo era excesivo. Solo necesitaba recopilar correos electrónicos. No necesitaba campañas, análisis ni gestión de suscriptores. Y quería ser dueño de mis datos.

Así que le pedí a Claude que lo construyera.

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 Configuración

Le di a Claude un token de API de Cloudflare a través de una variable de entorno y describí lo que quería: un formulario que recopile correos electrónicos y los almacene en un lugar que yo controlo.

Menos de 30 minutos después, tenía un boletín funcionando.

Lo que Claude Construyó

ComponenteTecnología
FormularioHTML + vanilla JS
BackendCloudflare Worker
AlmacenamientoCloudflare KV
SincronizaciónGitHub Actions

El formulario es un partial de Hugo que envía a /api/subscribe:

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

El backend es un Cloudflare Worker de 42 líneas que maneja validación, normalización, detección de duplicados y sanitización del referer:

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

La limitación de tasa proviene de la protección integrada de Cloudflare, no se necesita código adicional.

Un flujo de trabajo de GitHub Actions se ejecuta diariamente para sincronizar los suscriptores desde Cloudflare KV a un archivo JSONL en el repositorio. Mi lista de suscriptores vive en el control de versiones. Si Cloudflare cierra mi cuenta mañana, todavía tengo mis datos. Y están en un formato con el que los LLM pueden trabajar fácilmente.

El Flujo de Trabajo

Claude iteró localmente hasta que todo funcionó: envío de formulario, almacenamiento en KV, manejo de errores. Luego lo subió a una aplicación de vista previa, probó el flujo completo y lo fusionó con main.

El formulario se publicó mientras el tráfico aún fluía.

Todo esto se construyó en una sesión.

¿Por Qué No SaaS?

Buttondown es genial si lo necesitas. Yo no. Mis requisitos:

  1. Recopilar correos electrónicos
  2. Almacenarlos en un lugar que yo controle
  3. Lanzar rápido

Eso es todo. No necesito campañas por goteo ni pruebas A/B ni plantillas elegantes. Necesito una lista de direcciones de correo electrónico que poseo.

42 líneas de código en lugar de otra suscripción mensual. A veces la solución simple es la correcta.

La conclusión: imperfecto y en vivo supera a perfecto y planificado. Podría haber pasado horas evaluando servicios de boletines, comparando características, leyendo reseñas. En cambio, lancé algo en 30 minutos y seguí adelante.