20 — Sistema de emails transaccionales¶
Resumen¶
El SaaS envía 15 tipos de email profesionales, todos en 5 idiomas (es / en / fr / de / pt), HTML responsive + texto plano (multipart MIME).
No hay FastAPI ni Celery para emails. Todo corre en Django vía EmailMultiAlternatives + SMTP configurado en .env.
Regla de idioma (importante)¶
Todos los emails usan
users.locale— el idioma que el usuario tiene seleccionado en el client dashboard.
Flujo:
- El usuario cambia idioma con el selector de globo (header) → se guarda en
users.localevíaPATCH /api/dashboard/profile/me/. - También puede cambiarlo en Account → Profile → Language.
- Los webhooks Stripe (sin sesión de usuario) resuelven locale con
resolve_bot_owner_locale(bot)→ lee ellocaledel owner enusers. - Signup pasa
localeexplícito desde el formulario de registro.
Módulo central: backend_django/authentication/email_locale.py
resolve_user_locale(email) # por email del usuario
resolve_account_owner_locale(account_id)
resolve_bot_owner_locale(bot_obj) # webhooks Stripe
locale_from_request_user(user) # requests autenticados
Configuración SMTP¶
Variables en .env:
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=tu-email@gmail.com
EMAIL_HOST_PASSWORD=app-password-de-gmail
DEFAULT_FROM_EMAIL=BLIMX <noreply@tudominio.com>
FRONTEND_URL=https://app.tudominio.com
Test rápido:
cd backend_django && source venv/bin/activate
python manage.py shell <<'PY'
from authentication.utils import send_verification_email
send_verification_email('tu-email@gmail.com', 'test-token', locale='es')
PY
Catálogo completo de emails¶
Auth (authentication/utils.py)¶
| # | Trigger | Locale | |
|---|---|---|---|
| 1 | Verificación de cuenta | POST /api/auth/signup/ |
Del formulario signup |
| 2 | Reenvío verificación | POST /api/auth/resend-verification/ |
users.locale |
| 3 | Reset de contraseña | POST /api/auth/password-reset-request/ |
users.locale |
Pre-launch (authentication/emails_invitation.py)¶
| # | Trigger | Locale | |
|---|---|---|---|
| 4 | Solicitud recibida | POST /api/auth/invitation-request/ |
Del formulario (campo locale) |
| 5 | Código aprobado | Superadmin aprueba | invitation_request.locale |
| 6 | Solicitud rechazada | Superadmin rechaza | invitation_request.locale |
Ver 21-prelaunch-invitations.md.
Account (api/views_accounts.py)¶
| # | Trigger | Locale | |
|---|---|---|---|
| 7 | Verificación email account | POST /api/account/email-verification/request/ |
locale_from_request_user |
| 8 | Cierre de cuenta | POST /api/account/close/ |
locale_from_request_user |
Billing — Stripe webhooks (authentication/emails_billing.py + emails_lifecycle.py)¶
| # | Evento Stripe | Locale | |
|---|---|---|---|
| 9 | Factura pagada (PDF) | invoice.payment_succeeded |
users.locale del owner |
| 10 | Pago fallido | invoice.payment_failed |
resolve_bot_owner_locale |
| 11 | Suscripción finalizada | customer.subscription.deleted |
resolve_bot_owner_locale |
| 12 | Bienvenida plan activado | checkout.session.completed / subscription.created |
resolve_bot_owner_locale |
| 13 | Plan cambiado | customer.subscription.updated (plan_id distinto) |
resolve_bot_owner_locale |
Bot users (authentication/emails_lifecycle.py)¶
| # | Trigger | Locale | |
|---|---|---|---|
| 14 | Invitación a colaborar en bot | POST /api/dashboard/bot-users/ (create) |
Del formulario (locale del invitado) |
Arquitectura de módulos¶
backend_django/authentication/
├── utils.py ← _send_transactional_email(), verify, reset
├── email_locale.py ← resolve_user_locale(), resolve_bot_owner_locale()
├── emails_invitation.py ← pre-launch (3 emails)
├── emails_lifecycle.py ← welcome, account closed, plan changed, bot invite
└── emails_billing.py ← subscription ended, payment failed
backend_django/api/
├── views_stripe_webhook.py ← dispara emails 9-13
├── views_accounts.py ← email 8
├── services/invoice_service.py ← email 9 (PDF + email localizado)
Cómo añadir un email nuevo (~30 min)¶
- Añade strings en 5 idiomas en el módulo correspondiente (
emails_*.py). - Usa
_build_html_email()o_build_cta_html()para el HTML. - Llama
_send_transactional_email(subject, plain, html, email, lang, "EmailType"). - En la view/webhook, resuelve locale:
- Documenta el email en esta tabla.
Frontend: persistir idioma¶
El selector de globo (LanguageSelector.tsx) hace:
Al cargar el dashboard, DashboardLayout sincroniza i18n ← users.locale del perfil.
Lo que NO envía emails¶
| Evento | Por qué |
|---|---|
| Card expiring | Stripe lo envía nativamente (activar en Customer Portal settings) |
| Lead capture | Exterminado (Help Center, no ventas) |
| Chat / IA limit | Exterminado |
Troubleshooting¶
Ver 17-troubleshooting.md sección "Los emails no llegan".
Checklist rápido:
.envtieneEMAIL_HOST_USERyEMAIL_HOST_PASSWORDcorrectos.users.localetiene valor (SELECT email, locale FROM users LIMIT 5;).- Logs:
journalctl -u blimx-django -f | grep -i email. - Gmail: usar App Password, no la password normal.