InDraft

En vivo

Proyecto personal — un motor de borradores para LinkedIn basado en configuración. Genera borradores con tu voz según un cron, los revisás desde el celular, y nada se publica sin una aserción con passkey.

Rol
Solo — ingeniería, deploy
Período
may 2026 – Actual
Stack
  • Next.js 16
  • TypeScript
  • Vercel KV
  • Vercel Cron
  • OpenRouter
  • Claude Opus 4.7
  • Claude Haiku 4.5
  • WebAuthn (passkeys)
  • Resend
  • Pexels
  • Tailwind v4

Qué hace

Forkéas el repo, completás config.yml con tu párrafo de perfil, feeds RSS, horario de publicación y pilares de contenido, y yarn setup lo despliega en Vercel. A partir de ahí, un cron diario se ejecuta a las 14:00 UTC, filtra según tus días configurados y hora local, extrae una novedad de tus fuentes, redacta un post con tu voz y te envía un magic link por email.

Abrís la página de revisión desde el celular. Podés editar el borrador de forma conversacional hasta que estés conforme, luego tocás “Publicar” — Face ID o Touch ID, y queda publicado en LinkedIn.

Por qué lo construí

La calidad de un post en LinkedIn depende de la voz y la oportunidad. La mayoría de las herramientas de automatización destruyen ambas: generan texto genérico de IA y publican según un horario que no tiene nada que ver con lo que está pasando ese día. InDraft intenta preservarlas — tu voz es el anclaje principal del modelo, el horario se basa en fuentes frescas, y siempre hay un humano en el circuito antes de que se publique algo.

También es un motor genérico basado en configuración, no un SaaS. Nada personal — voz, fuentes, credenciales — vive en el repo. Forkéalo, apuntá tu config a tus cosas, desplegá.

Cómo funciona

Arquitectura

Vercel Cron (daily 14:00 UTC)
   | day/hour filter
Collector: RSS + GitHub
   |
Generator + anti-AI-tell linter
   |
Media: Pexels or owner upload
   |
Vercel KV
   |
Resend: magic-link email to /review
   |
Conversational chat edits
   |
WebAuthn passkey assertion
   |
LinkedIn Posts API

La máquina de estados en src/lib/state/drafts.ts es la única escritora del estado del borrador y el límite de seguridad central. La única forma de llegar a la LinkedIn Posts API es a través de una aserción WebAuthn verificada — ningún camino programado o automático publica. Un test e2e en tests/e2e/publish-guard.test.ts lo prueba a nivel de máquina de estados: un borrador que no esté en PENDING_REVIEW nunca puede pasar a PUBLISHED, y una aserción capturada no puede reutilizarse contra un borrador editado porque el challenge está vinculado a la versión del borrador.

Valores por defecto clave

  • link_placement: "none" — investigaciones actuales sobre el algoritmo de LinkedIn muestran que los links externos reducen el alcance. InDraft muestra la URL fuente dentro de la UI de revisión. Sobreescribilo por post si querés.
  • Modelo frontier para redactar, modelo económico para ranking — Claude Opus 4.7 redacta porque la calidad anti-muletillas de IA se degrada con modelos más débiles; Claude Haiku 4.5 maneja el ranking de fuentes y tareas mecánicas. Ambos enrutados a través de OpenRouter BYOK.
  • Token de LinkedIn de 60 días, sin refresh — las apps de LinkedIn self-serve emiten tokens de 60 días sin posibilidad de refresh. InDraft guarda issued_at en KV, envía un aviso de reautenticación ~7 días antes del vencimiento, y muestra un banner de “reconectar LinkedIn” en la UI de revisión.

Modelo de seguridad

  • El único camino de publicación es un clic, luego una aserción WebAuthn, luego una transición en el servidor con un publishProof derivado. El challenge de la aserción es sha256(draft.id || draft.version || draft.body), así que una aserción capturada no puede reutilizarse.
  • Los magic links están firmados con HMAC, son de un solo uso (nonce guardado en KV con TTL) y vencen a las 24 horas por defecto.
  • El endpoint público /access siempre envía el email únicamente a la NOTIFY_TO_ADDRESS configurada — nunca a un destino provisto por un atacante.
  • Las sesiones son cookies HttpOnly, Secure, SameSite=Lax vinculadas a un id de borrador específico.
  • .gitignore excluye config.yml, .env* y .vercel/. El script de setup envía los secretos solo por el flujo prompt -> vercel env add — nunca a disco.

Estado

El motor está completo en funcionalidades y tiene licencia MIT. El repo es el entregable; mi deploy personal está en indraft.dev.

Preguntas

¿Qué es InDraft?

InDraft es un motor open-source de borradores para LinkedIn. Un job programado extrae novedades de tus feeds RSS y tu actividad en GitHub, elige una, y redacta un post con tu voz. Lo revisás desde el celular, lo editás de forma conversacional, y el post solo se publica después de una aserción WebAuthn con passkey vinculada a ese borrador exacto.

¿Por qué una passkey para publicar?

No existe ningún camino de publicación automática en el código. Cada post publicado pasa por una aserción WebAuthn vinculada a un sha256 del id, versión y cuerpo del borrador — así una aserción capturada no puede reutilizarse contra un borrador editado. La máquina de estados en src/lib/state/drafts.ts es la única escritora del estado del borrador y garantiza esta propiedad a nivel de tipos.

¿Cómo preserva InDraft tu voz?

Un bloque de texto libre en config.yml es el anclaje principal del modelo. El modelo de redacción es Claude Opus 4.7 vía OpenRouter BYOK, porque la calidad anti-muletillas de IA se degrada con modelos más débiles. Un linter separado rechaza el abuso de guiones largos, "let's dive in" y otras muletillas antes de que el borrador llegue a la UI de revisión.

¿Por qué omitir el link por defecto?

Investigaciones sobre el algoritmo de LinkedIn en 2026 muestran que los posts con links en el cuerpo pueden perder hasta un 60% de alcance, y el viejo truco del "link en el primer comentario" hasta un 80%. El valor por defecto de InDraft es link_placement = "none" — la URL fuente se muestra dentro de la UI de revisión. Podés sobreescribirlo por post.