Newsletter semanal de la comunidad devsChile: resumen de la actividad de la semana, pegas nuevas y los links más útiles compartidos en los canales de contenido.
🔗 semanario.devschile.cl — landing de suscripción y archivo de ediciones.
semanario/
├── template.html # Diseño base del correo (email-safe, 600px)
├── ediciones/
│ └── YYYY-MM-DD/
│ ├── newsletter.html # Edición de la semana lista para enviar
│ ├── resumen.md # Fuente de datos: links y contexto por canal
│ └── links.json # Tags de cada link, para el buscador
│
├── index.html # Landing de suscripción + buscador de links
├── css/, js/, assets/ # Estáticos de la landing
├── scripts/build.mjs # Arma dist/ para Netlify (incluye buscar.json)
├── scripts/tags.json # Vocabulario de tags permitido
├── scripts/prompt-links.md # Prompt del bot para etiquetar links
├── netlify/functions/ # Endpoints de alta, confirmación y baja
└── db/ # Esquema de suscriptores (Neon/Postgres)
Tres pasos, en tres momentos distintos — en ese orden importa: la edición queda publicada en la web horas antes de que se envíe el correo, así que el link "ver en el navegador" del correo siempre apunta a una página que ya existe.
Un cron del bot (Hermes/Pudú) revisa el historial de Slack de la semana (domingo → sábado) y:
- Lee los canales de contenido: #ai, #musiqueria, #juegos, #lifehacks, #diy, #ux, #desarrollo, #cultura, #comunidad, #eventos-juntas, #frontend, #backend, #liderazgo, #mascotas, #moneas, #persa, #remoto.
- Arma el resumen de actividad (mensajes por día, día más activo, personas nuevas).
- Cuenta pegas:
se publicaron [XX] pegas nuevas en pegas.devschile.cl y [X] anuncios en #trabajos. - Filtra links útiles (sin memes) y genera frases con contexto por canal.
- Revisa #anuncios: si hubo mensajes en la semana, va como bloque destacado al inicio; si no, se omite.
- Rellena los
{{PLACEHOLDERS}}detemplate.html(el diseño base, email-safe, 600px) y escribe el resultado ya renderizado —sin placeholders— enediciones/YYYY-MM-DD/newsletter.html+resumen.md(la fuente de datos en texto plano, para la versión texto del correo). - Corre
node scripts/indexar_links.mjs YYYY-MM-DDy etiqueta los links delinks.jsonsiguiendoscripts/prompt-links.md. - Pushea a
main.
Ese push dispara un build y deploy de Netlify. scripts/build.mjs arma el
directorio dist/ que se publica en
semanario.devschile.cl:
-
copia los estáticos de la landing (
css/,js/,assets/), -
transforma cada
ediciones/YYYY-MM-DD/newsletter.htmlendist/newsletter/YYYY-MM-DD/index.html— la versión web de esa edición, -
genera la lista de ediciones leyendo las carpetas de
ediciones/e inyecta las últimas 4 en la portada y el archivo completo en/newsletter/. Nadie edita ese listado a mano. -
genera
dist/buscar.json, el índice del buscador (ver más abajo).
| Ruta | Qué es |
|---|---|
/ |
Landing con el formulario de suscripción y el buscador |
/buscar.json |
Índice de links de todas las ediciones |
/newsletter/ |
Archivo con todas las ediciones |
/newsletter/YYYY-MM-DD/ |
Una edición, en su versión web |
/ediciones/* |
Redirige (301) a /newsletter/* |
Este paso ocurre el lunes en la noche, apenas se pushea — no el martes cuando se manda el correo. Para cuando el envío ocurre, la página ya lleva horas viva.
scripts/enviar.mjs (lo corre el cron del bot; el workflow de
.github/workflows/enviar.yml es solo respaldo manual) toma la edición más
reciente de ediciones/ y le hace dos inyecciones antes de mandarla por
Mailgun a los suscriptores confirmed de Neon:
- Ver en el navegador (marcador
<!--VER_HTML-->en el header del template): el mismo link para todos, apunta a la página que el paso 2 ya publicó —{SITE_URL}/newsletter/YYYY-MM-DD/. - Baja en un clic (marcador
<!-- BAJA_LINK -->en el footer): un link distinto por destinatario, con suunsubscribe_tokende la base — más el headerList-Unsubscribepara el botón nativo de Gmail/Outlook.
Si la edición más reciente tiene más de 3 días, el script aborta (para no reenviar una edición vieja). Un fallo de envío individual no corta la lista.
"Buscar en ediciones anteriores", en la portada, busca entre los links de
todas las ediciones por texto (sin importar tildes ni mayúsculas), canal y
tag. Funciona entero en el navegador (js/buscar.js + js/buscar-core.js)
contra /buscar.json, que se descarga recién cuando el buscador entra en
pantalla. Acepta links directos: /?q=rust&canal=%23ai&tag=lenguajes.
De dónde sale cada dato:
- URL, título, canal y contexto: del
resumen.mdde cada edición, que el build vuelve a leer en cada deploy (scripts/lib/indice-links.mjs). Se indexa cada link markdown de las viñetas; el párrafo de conteo de pegas y la metadata en comentarios<!-- SEMANARIO_* -->se omiten. - Tags: de
links.json, que generascripts/indexar_links.mjsy completa el bot. El build solo toma los tags de ese archivo, solo si existen enscripts/tags.jsony como máximo 4 por link; el resto lo descarta con un aviso. Una edición sinlinks.jsonigual aparece en el buscador, sin tags.
Para agregar un tag nuevo, súmalo a scripts/tags.json (slug → nombre visible).
El alta la maneja una Netlify Function que escribe en Postgres (Neon). El flujo
es double opt-in: alta → pending, Mailgun manda el correo de confirmación,
se confirma con el token del link → confirmed. Cada correo de edición lleva
su token de baja.
| Endpoint | Qué hace |
|---|---|
POST /api/subscribe |
Da de alta un correo en pending y dispara el correo de confirmación (Mailgun) |
GET /api/confirmar?token= |
Confirma la suscripción |
GET /api/baja?token= |
Da de baja |
Si el envío por Mailgun falla, el alta en la base no se revierte — queda
como pending y el error solo se loguea (netlify/functions/subscribe.mjs).
npm install
cp .env.example .env # completar DATABASE_URL (Neon) y las variables MAILGUN_*
npm run db:schema # crea la tabla subscribers (idempotente)
npm run dev # build + netlify dev en localhost:8888Contenido: #ai, #desarrollo, #frontend, #backend, #diy, #lifehacks, #ux, #juegos, #musiqueria, #cultura, #comunidad, #eventos-juntas, #mascotas, #moneas, #persa, #remoto, #liderazgo. Pegas: #trabajos (bot d4rkmul → pegas.devschile.cl). Destaque: #anuncios (solo si tuvo actividad la semana).
Generado por la comunidad, para la comunidad. 🦌