Resumen ejecutivo
Documentación técnica del flujo de email del ecosistema CreaRackSL tras la migración s69 (18-05-2026). Cubre los 3 origenes de envío (Resend desde Django · Resend desde MCP workspace · routine Maintenance-Weekly), el esquema de 8 bandejas Zoho de recepción, y los puntos de configuración panel-por-panel para los avisos de servicios externos.
Versión accesible para todo el staff (sin jerga): [[workspace—guias—emails-equipo]].
Arquitectura global
┌──────────────────────────────────────────────────────────────────────────┐
│ ORIGEN DE EMAILS │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────┐ ┌──────────────────────┐ │
│ │ CreaRack-Pro (Django) │ │ Workspace (Worker) │ │
│ │ django-anymail │ │ /api/mcp │ │
│ │ + Resend backend │ │ send_maintenance_email
│ └───────────┬───────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ Resend API │ │
│ │ FROM domain: crearack.com (verified) │ │
│ │ RESEND_API_KEY (CF + Dokploy) │ │
│ └─────────────────┬──────────────────────┘ │
│ │ │
└─────────────────────────┼────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────┐
│ DESTINO DE EMAILS │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ Zoho Mail · dominio esfericlabs.com (8 grupos internos) │
│ Gmail externos (paneles de servicios cuyo aviso aún no se migró) │
│ Usuarios finales SaaS (Allauth: registro/reset/mfa via crearack.com) │
│ │
└──────────────────────────────────────────────────────────────────────────┘
Componentes
1. Allowlist Resend en workspace MCP
Archivo: functions/api/mcp/handlers/email.ts
ALLOWED_RECIPIENTS es un Set<string> con allowlist hardcoded. La tool send_maintenance_email rechaza cualquier to que no esté listado. Defensa contra prompt injection.
Lista activa (commit 319fe42):
const ALLOWED_RECIPIENTS = new Set<string>([
// Personal (testing + compat retro)
'edudomo2@gmail.com',
// Esferic Labs individuales
'tfuentes@esfericlabs.com',
'dfuentes@esfericlabs.com',
'eesquembri@esfericlabs.com',
// Grupos canónicos s69
'logworkspace@esfericlabs.com',
'logcrearack@esfericlabs.com',
'infra@esfericlabs.com',
'dev-platform@esfericlabs.com',
'security@esfericlabs.com',
'dev-billing@esfericlabs.com',
'factu@esfericlabs.com',
'legal@esfericlabs.com',
]);
FROM hardcoded: Mantenimiento CreaRackSL <noreply@crearack.com> (línea 15). Display name distinto del SaaS (CreaRack Pro) para separar visualmente ambos flujos.
2. Auth del endpoint MCP workspace
El endpoint /api/mcp valida Bearer en 2 capas:
Capa 1: middleware global (functions/api/_middleware.ts):
// Check 2: Bearer token (MCP / programmatic requests)
if (env.MAINTENANCE_AGENT_TOKEN && timingSafeEqual(env.MAINTENANCE_AGENT_TOKEN, token)) {
return next();
}
const tokenPairs = (env.MCP_TOKENS || '').split(',').filter(Boolean);
for (const pair of tokenPairs) {
const [, t] = pair.split(':');
if (t && timingSafeEqual(t, token)) {
return next();
}
}
Capa 2: handler MCP (functions/api/mcp/index.ts::authenticate):
if (env.MAINTENANCE_AGENT_TOKEN && token === env.MAINTENANCE_AGENT_TOKEN) {
return { ok: true, user: 'maintenance-agent' };
}
// ... fallback a MCP_TOKENS estándar ...
Defensa adicional: CF Access bloquea acceso a workspace.crearack.com/* antes incluso de llegar al middleware. Para que el routine Maintenance-Weekly pase, usa un CF Access Service Token (Client ID + Secret en headers CF-Access-Client-Id/Secret) con policy “Service Auth” en la app de CF Access “Workspace MCP API”.
3. Routine Maintenance-Weekly (claude.ai)
Routine ID: trig_015rwvGukwjGWdMtEX4pKquU
Schedule: 0 8 * * 1 (lunes 10:00 Madrid CEST · 09:00 CET)
Prompt version: v1.5 (s69 18-05-2026)
Modelo: claude-sonnet-4-6
Repo checkout: CreaRackSL/CreaRackSL-workspace
Patrón crítico: el routine corre headless y el connector MCP workspace-crearack-com requiere OAuth interactivo que NO se puede completar. El agente Sonnet solo ve authenticate/complete_authentication, NO las tools reales del MCP. Detalle en [[footgun_routine_claude_ai_mcp_oauth_headless]].
Solución: el prompt instruye al agente a hacer curl directo al endpoint MCP con 3 headers (CF Access + Bearer estático) en lugar de buscar el tool del connector. Ver bloque bash completo en la sección 1 del prompt del routine.
Destinatario canónico (v1.5): logworkspace@esfericlabs.com (grupo Zoho Dani+Edu).
4. Django · alertas runtime SaaS
Archivo: config/settings/production.py
DEFAULT_FROM_EMAIL = "CreaRack Pro <noreply@crearack.com>"
SERVER_EMAIL = "CreaRack Pro <server@crearack.com>"
ADMINS = [
("Ops CreaRack", os.getenv("ADMIN_EMAIL", "logcrearack@esfericlabs.com")),
]
MANAGERS = ADMINS
ADMINS recibe stack traces de errores 500 (mecanismo nativo Django via mail_admins). Default logcrearack@esfericlabs.com desde commit 9572d5e9 (s69). Override opcional via env var ADMIN_EMAIL en Dokploy (PROD + STAGE) si se quiere split de destinatarios entre entornos.
Email transaccional al usuario final SaaS: DEFAULT_FROM_EMAIL se mantiene en noreply@crearack.com. Se usa en allauth (registro, password reset, MFA, magic links). Estos van al usuario, no a nosotros.
5. CF Pages env vars del workspace
| Variable | Tipo | Función | Notas |
|---|---|---|---|
RESEND_API_KEY | Secret | API key de Resend | Usado por handler email.ts |
MCP_TOKENS | Secret | Bearer tokens compartidos name:token,name:token | Auth genérica MCP |
MAINTENANCE_AGENT_TOKEN | Secret | Bearer dedicado del routine | s69 · revocable independiente, mapea user maintenance-agent en activity_log |
6. Dokploy env vars de CreaRack-Pro (PROD + STAGE)
| Variable | Default código | Override Dokploy | Notas |
|---|---|---|---|
ADMIN_EMAIL | logcrearack@esfericlabs.com | no seteado actualmente | Setear solo si quieres distinguir destinatario PROD vs STAGE |
RESEND_API_KEY | - | configurado | Usado por django-anymail[resend] |
Esquema de bandejas Zoho
8 grupos en esfericlabs.com + 3 producto en crearack.com. Tabla detallada con asignaciones, qué llega y Streams on/off: [[workspace—guias—emails-equipo]] o memoria reference_email_groups_scheme.
| Bandeja | Grupo | Streams | Allowlist Resend |
|---|---|---|---|
logworkspace | Dani+Edu | OFF | ✅ |
logcrearack | Dani+Edu | OFF | ✅ |
infra | Dani+Edu | ON | ✅ |
dev-platform | Dani+Edu | ON | ✅ |
security | Dani+Edu | ON | ✅ |
dev-billing | Dani+Edu | OFF | ✅ |
factu | Txell | OFF | ✅ |
legal | Txell | OFF | ✅ |
Migración paneles externos (Fase 2)
Estado: en curso. Task workspace #59 (Edu, due 25-05-2026) con checklist de 11 servicios. Detalle del mapeo servicio → bandeja en la task.
Servicios fuera de scope (no envían avisos operativos o son personales):
- Anthropic Plan Max × 3 (cuentas personales)
- Google OAuth + GitHub OAuth (sin avisos)
- Claude GitHub App (sin avisos)
- Dokploy + VictoriaMetrics (self-host)
- SpinetiX (per-cliente)
- DeepSeek + groupdocs (retirados Plan Servicios Fase B)
GitHub · routing de notificaciones (status quo: todo a dev-platform@)
Hoy toda la actividad GitHub (PRs/issues/Dependabot/Security) llega a dev-platform@esfericlabs.com sin distinción por tipo. No es una limitación técnica — es que el routing granular aún no se ha configurado (forma parte de la Fase 2 paneles, task #59).
Actualización s74/s82:
CreaRackSLya es una Organization GitHub (migrada de cuenta User en s74, 19-05-2026). Por tanto el routing granular per-repo / per-tipo (security separado de PRs, billing separable) está disponible a nivel Org. La premisa anterior de este doc (“es una cuenta User, no se puede”) quedó obsoleta con la migración. La memoriareference_github_crearacksl_es_userestá obsoleta y debe marcarse/borrarse.
Estado actual
| Tipo notification | Configurado en | Destino |
|---|---|---|
| Default notifications (PRs/issues/mentions/Dependabot/Security) | Org CreaRackSL → notifications + per-user /settings/notifications | dev-platform@esfericlabs.com |
| Billing | Org CreaRackSL → Billing settings → “Billing email” | (decidir en Fase 2 final · dev-billing@ o factu@) |
⚠ Tras la migración a Org, verificar a dónde van realmente las notificaciones del Org (la config de la cuenta User antigua no se hereda automáticamente).
Opciones de routing granular (ahora que es Org)
Si el ruido en dev-platform@ se hace inmanejable o un security alert crítico se nos cuela, ahora que es Organization hay caminos nativos que antes no existían:
- A · Status quo (actual) — todo a
dev-platform@. El volumen con 3 repos pequeños es ~1-2 alerts/semana. Si Dani+Edu lo absorben sin perder eventos, no tocar. - B · Org notification routing nativo — separar security/vulnerability alerts a
security@y billing afactu@/dev-billing@desde los settings de la Org (disponible al ser Org). Es la vía limpia ahora. - C · Filtros Zoho (personal o grupo admin) — separación visual sin tocar GitHub, útil como complemento.
- D · Webhook GitHub → workspace MCP — para enrutar
security_advisory/dependabot_alertasecurity@viasend_maintenance_email. Solo si se quiere control fino con presupuesto dev.
Recomendación actual
Quedarse en A (status quo) hasta cerrar la Fase 2 paneles (task #59), y entonces evaluar B (routing Org nativo) como vía preferente al ser ya Organization.
Troubleshooting
El routine Maintenance-Weekly entrega Gmail draft en vez de Resend
Histórico: a partir de s69 (18-05-2026) el routine envía Resend directo via bypass curl al
/api/mcp(send_maintenance_email). Este troubleshooting documenta el patrón pre-s69 / regresión por si el prompt vuelve a una versión anterior.
Síntoma: en el .md del informe, sección § Envío aparece “Resend ID: r5132042…” (formato Gmail draft, no UUID Resend).
Causa: el agente Sonnet del routine intentó usar el tool del connector workspace-crearack-com (que en runs headless NO expone las tools reales) y cayó al fallback mcp__Gmail__create_draft.
Fix: verificar que el prompt del routine sigue siendo v1.5+ con el bloque curl explícito y el antipatrón #2 (“NO uses mcp__Gmail__create_draft”). Ver historial completo en la sesión s69 (18-05-2026).
CF Access devuelve 302 al routine
Síntoma: el curl desde el routine devuelve HTTP/1.1 302 Found redirigiendo a crearacksl.cloudflareaccess.com/cdn-cgi/access/login/.... JWT del meta tag dice service_token_status: false.
Causa: CF Access no reconoce el Service Token. La policy del Service Token debe existir con action “Service Auth” (no “Allow” — con “Allow” se ignora silenciosamente, footgun s78).
Arquitectura s78 (21-05-2026): se eliminó la app específica “DR Backup Service” (
/api/maintenance/*) porque, al ser más específica de path, tumbaba el SSO de la app principal “CreaRackSL Workspace” sin compartir sesión (footguncf_access_app_path_specificity). Sus policies se consolidaron en la app principal mediante una policy “Service Tokens (crons)” con action “Service Auth”. Regla general: dos apps cubriendo el mismo dominio con paths distintos → la más específica gana y NO comparte sesión con la principal.
Fix: añadir/verificar la policy “Service Tokens (crons)” (action “Service Auth”) en la app “CreaRackSL Workspace” con el Service Token correcto. Las CF Access apps activas que cubren paths de workspace.crearack.com:
| App | Cubre path | Notas |
|---|---|---|
| Workspace MCP API | /api/mcp | específica |
| API Biblioteca | /api/biblioteca/ | específica |
| Workspace Discovery | /.well-known | específica |
| Workspace OAuth | /oauth | específica |
| CreaRackSL Workspace | host completo | principal · incluye policy “Service Tokens (crons)” para /api/maintenance/* (s78) |
Algún email no llega al inbox de un grupo Zoho
- Verifica que el email destinatario está en
ALLOWED_RECIPIENTSdelemail.ts. Si no, añadirlo y redeployar workspace. - Verifica en Resend dashboard que el envío tuvo
status: sentcon UUID válido. - Si Resend dice OK pero Zoho no lo recibió: revisar logs de Zoho (admin) o spam/quarantine.
- Allowlist incluye todos los
@esfericlabs.comnecesarios desde commit319fe42.
Stack trace de error 500 Django no llega
- Verifica que
EMAIL_BACKENDestá enanymail.backends.resend.EmailBackenden production. - Verifica que
RESEND_API_KEYestá seteado en Dokploy env del contenedorcrearack-pro-zcmvsl-web-1(PROD) o equivalente STAGE. - Verifica que
ADMINSse ha aplicado tras el redeploy del commit9572d5e9(docker exec ... python -c "from django.conf import settings; print(settings.ADMINS)"). - Si Dokploy env tiene
ADMIN_EMAILcon valor antiguo, override desactiva el default.
Historial de cambios relevantes
| Fecha | Sesión | Cambio | Commit |
|---|---|---|---|
| 2026-05-13 | s60 | Tool send_maintenance_email creada · Resend backend MCP workspace | f9aba6a |
| 2026-05-18 mañana | s69 | Routine v1.4 con bypass MCP via curl directo · CF Access Service Token + MAINTENANCE_AGENT_TOKEN | 2478e5f + c7f2910 |
| 2026-05-18 tarde | s69 | Esquema 8 grupos Zoho · allowlist Resend ampliada · ADMINS default a logcrearack@ · routine v1.5 | 319fe42 + 9572d5e9 |
| 2026-05-19 | s74 | Migración GitHub User → Organization CreaRackSL (routing granular per-tipo ahora disponible) | - |
| 2026-05-21 | s78 | CF Access: app “DR Backup Service” eliminada · policy “Service Tokens (crons)” (Service Auth) consolidada en app principal | - |
| 2026-05-24 | s82 | Auditoría pre-onboardings: sección GitHub reescrita a realidad Org · CF Access actualizado a s78 · frontmatter last_verified/lifecycle añadidos | - |
| 2026-05-25 (pendiente) | — | Cierre Fase 2 paneles externos (task workspace #59) | - |
Véase también
- [[entity—mcp—tool—send-maintenance-email]]
- [[entity—ops—catalogo-servicios-externos]]
- [[workspace—guias—emails-equipo]]