CreaRack-SL

Email routing architecture · stack completo de envío y destinatarios

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

VariableTipoFunciónNotas
RESEND_API_KEYSecretAPI key de ResendUsado por handler email.ts
MCP_TOKENSSecretBearer tokens compartidos name:token,name:tokenAuth genérica MCP
MAINTENANCE_AGENT_TOKENSecretBearer dedicado del routines69 · revocable independiente, mapea user maintenance-agent en activity_log

6. Dokploy env vars de CreaRack-Pro (PROD + STAGE)

VariableDefault códigoOverride DokployNotas
ADMIN_EMAILlogcrearack@esfericlabs.comno seteado actualmenteSetear solo si quieres distinguir destinatario PROD vs STAGE
RESEND_API_KEY-configuradoUsado 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.

BandejaGrupoStreamsAllowlist Resend
logworkspaceDani+EduOFF✅
logcrearackDani+EduOFF✅
infraDani+EduON✅
dev-platformDani+EduON✅
securityDani+EduON✅
dev-billingDani+EduOFF✅
factuTxellOFF✅
legalTxellOFF✅

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: CreaRackSL ya 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 memoria reference_github_crearacksl_es_user está obsoleta y debe marcarse/borrarse.

Estado actual

Tipo notificationConfigurado enDestino
Default notifications (PRs/issues/mentions/Dependabot/Security)Org CreaRackSL → notifications + per-user /settings/notificationsdev-platform@esfericlabs.com
BillingOrg 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 a factu@/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_alert a security@ via send_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 (footgun cf_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:

AppCubre pathNotas
Workspace MCP API/api/mcpespecífica
API Biblioteca/api/biblioteca/específica
Workspace Discovery/.well-knownespecífica
Workspace OAuth/oauthespecífica
CreaRackSL Workspacehost completoprincipal · 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_RECIPIENTS del email.ts. Si no, añadirlo y redeployar workspace.
  • Verifica en Resend dashboard que el envío tuvo status: sent con 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.com necesarios desde commit 319fe42.

Stack trace de error 500 Django no llega

  • Verifica que EMAIL_BACKEND está en anymail.backends.resend.EmailBackend en production.
  • Verifica que RESEND_API_KEY está seteado en Dokploy env del contenedor crearack-pro-zcmvsl-web-1 (PROD) o equivalente STAGE.
  • Verifica que ADMINS se ha aplicado tras el redeploy del commit 9572d5e9 (docker exec ... python -c "from django.conf import settings; print(settings.ADMINS)").
  • Si Dokploy env tiene ADMIN_EMAIL con valor antiguo, override desactiva el default.

Historial de cambios relevantes

FechaSesiónCambioCommit
2026-05-13s60Tool send_maintenance_email creada · Resend backend MCP workspacef9aba6a
2026-05-18 mañanas69Routine v1.4 con bypass MCP via curl directo · CF Access Service Token + MAINTENANCE_AGENT_TOKEN2478e5f + c7f2910
2026-05-18 tardes69Esquema 8 grupos Zoho · allowlist Resend ampliada · ADMINS default a logcrearack@ · routine v1.5319fe42 + 9572d5e9
2026-05-19s74Migración GitHub User → Organization CreaRackSL (routing granular per-tipo ahora disponible)-
2026-05-21s78CF Access: app “DR Backup Service” eliminada · policy “Service Tokens (crons)” (Service Auth) consolidada en app principal-
2026-05-24s82Auditorí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]]