Despliegue
Vercel (frontend y backend), cron, Upstash y base de datos.
En esta página
BloomX está pensado para Vercel (frontend y backend son proyectos Next.js independientes), pero funciona en cualquier host de Node que ejecute next start.
Base de datos#
- PostgreSQL para el frontend (
DATABASE_URL) y otra para el backend. Neon, Supabase, RDS… cualquiera. - Ejecuta
npm run db:ensureantes de desplegar contra la base de destino (en Vercel corre solo enprebuild, que necesitaDATABASE_URLen el entorno de build). Sin las tablasUserMfa,RevokedSession,AuditEvent,Rule,RuleRun, etc. algunas funciones se degradan y el login de un administrador responde 503. db:ensureusa advisory locks por transacción pensados para el pooler de Neon (pgbouncer en modo transacción), pero no se probó contra un Neon real. Si tienes problemas, ejecútalo con la conexión directa (no la del pooler).- El backend no tiene
ensure: aplica su esquema connpm run prisma:push(usaprisma/schema_push.prisma). Revisa siempre lo queprisma db pushpropone antes de aceptar cambios destructivos.
Frontend en Vercel#
- Importa el repositorio
bloomxcomo proyecto (Framework: Next.js). Build por defecto:npm run build(ejecutaprebuild→db:ensurey luegonext build). - Define las variables de entorno del mínimo de producción. En especial
NEXTAUTH_SECRET(propio),NEXT_PUBLIC_APP_URL(https) yNEXT_PUBLIC_BACKEND_URL(https). - Asigna tu dominio y confirma que
TOP_DOMAINcoincide con el tenant que registraste en el backend. - Despliega y comprueba
GET /api/config.
NEXT_PUBLIC_* se incorporan en el build: si las cambias, vuelve a desplegar. Si dejas NEXT_PUBLIC_BACKEND_URL vacío se usa https://backend.bloomx.arubik.dev (y en dos sitios http://…): fíjala siempre.
Backend compartido en Vercel#
- Proyecto aparte con la carpeta
bloomx-backendcomo raíz. No tienevercel.json: usa los valores por defecto de Next.js. - Variables: mínimo del backend. No definas
NEXTAUTH_SECRET,INTERNAL_SECRET,EXTENSION_HOOKS_SECRETniFRONTEND_INTERNAL_URL. - Aplica el esquema con
npm run prisma:pushy publica las extensiones (Crear una extensión). - El backend NO necesita ninguna clave:
services.maily el resto de servicios del host funcionan con laexecutionGrantque firma cada instancia con suBLOOMX_DOMAIN_PRIVATE_KEY. No definasBACKEND_SIGNING_PRIVATE_KEY(legado deprecado). Cada instancia que quiera GoogleLib, IA por puente o rutas de extensión necesita su clave de dominio: ver Requisito: la clave de dominio. - Registro abierto: si tu backend es privado, define
ALLOW_OPEN_REGISTRATION=false.
Cron#
Define CRON_SECRET en el proyecto del frontend: Vercel Cron envía automáticamente Authorization: Bearer <CRON_SECRET> a los crons declarados. El código solo comprueba el Bearer (no que la llamada venga de Vercel).
{
"framework": "nextjs",
"crons": [
{ "path": "/api/cron/run", "schedule": "0 6 * * *" }
]
}| Endpoint | Qué hace | Programación |
|---|---|---|
/api/cron/run | Recordatorios de calendario (eventos en los próximos 15 min; ejecución cada 5 min como máximo), reglas sobre correos recientes (catch-up de 2 días, lotes de 200), en modo global para hasta 50 usuarios. POST con sesión ejecuta solo para ese usuario (lo hace el navegador cada 5 min con la pestaña visible) | Declarado: diario, 06:00 UTC |
/api/cron/elixir | Worker de campañas de Elixir: hasta 5 campañas en marcha, presupuesto de 50 s (maxDuration 60), reanudable. Acepta ?campaign=<id>. Se encadena a sí mismo mientras queden filas listas (hasta 400 saltos) | No declarado: necesita un cron cada minuto (plan de Vercel que lo permita) o un pinger externo con Authorization: Bearer <CRON_SECRET>. También avanza por el encadenamiento y el sondeo de la interfaz |
/api/admin/retention | Purga por retención (Seguridad) | No declarado: añade un cron diario, por ejemplo 0 3 * * * |
# Recordatorios y reglas (modo global, hasta 50 usuarios). Vercel Cron lo llama con GET y Bearer automáticamente.
curl -sS "https://mail.tu-dominio.com/api/cron/run" -H "Authorization: Bearer $CRON_SECRET"
# Campañas de Elixir en segundo plano (GET o POST). Procesa hasta 5 campañas 'running' (~50 s)
curl -sS "https://mail.tu-dominio.com/api/cron/elixir" -H "Authorization: Bearer $CRON_SECRET"
# Retención (añade ?dryRun=1 para solo contar)
curl -sS "https://mail.tu-dominio.com/api/admin/retention?dryRun=1" -H "Authorization: Bearer $CRON_SECRET"Elixir sin cron
Sin un cron propio, las campañas en segundo plano solo avanzan mientras haya encadenamiento o la interfaz esté abierta haciendo tick. Para campañas fiables configura un pinger externo (o un cron por minuto) contra /api/cron/elixir. La frecuencia de crons depende de tu plan de Vercel: consulta sus límites actuales.
Los tokens de baja y el resto de endpoints públicos (/api/cron, /api/webhooks, /api/assets) no exigen sesión: se protegen con su propio secreto o firma.
Upstash Redis (opcional)#
Sin Upstash el rate limit es por instancia (memoria). En Vercel cada invocación puede caer en otra instancia, así que para un límite real crea una base en Upstash, copia la URL REST y el token a UPSTASH_REDIS_REST_URL y UPSTASH_REDIS_REST_TOKEN. Si Redis falla, RATE_LIMIT_ON_ERROR=memory (por defecto) cuenta en memoria; nunca bloquea a todos. El rate limit del backend siempre es en memoria.
HTTPS y cabeceras#
- Usa siempre https: la cookie de sesión
__Host-…exigeSecurey el frontend emite HSTS conpreload. - Si quitas el prefijo
__Host-o cambias de dominio, las sesiones existentes se invalidan (verSESSION_COOKIE_*en Variables). - Detrás de un proxy propio, haz que sobrescriba
x-forwarded-for(el rate limit lo usa) yx-forwarded-host.
Después del despliegue#
- Configura los webhooks de Resend y el DNS (Correo: Resend y DNS).
- Rota cualquier credencial que haya estado en texto plano en algún archivo o historial.
- Repasa Operación (backups y pruebas).