feat(email): multi-provider con fallback chain (v2.0.0) - #25
Merged
Conversation
Reemplaza el `ResendProvider` hardcodeado que leía `RESEND_API_KEY` por un subsistema multi-provider donde el platform_owner configura uno o varios providers (Resend, SendGrid, Mailgun, SMTP) desde la admin SPA en `/admin/platform/email-providers`. El dispatcher recorre los providers activos por prioridad ascendente y hace fallback al siguiente cuando el actual falla con un error retryable (`ProviderUnavailable` o `ProviderRateLimited`). Cambios principales: - Nuevo schema `app.email_providers` + `app.email_dispatch_log` (`copiloto_core/platform_schema/30-email-providers.sql`). RLS platform-owner-only. - Nuevo paquete `copiloto_core.email` con `EmailDispatcher`, `EmailMessage`, factory y 4 adapters concretos. Excepciones tipadas (`ProviderUnavailable`/`RateLimited`/`InvalidConfig`/`Rejected`) que el dispatcher inspecciona para decidir fallback. - CRUD endpoints `/v1/platform/email-providers/*` + `POST .../test` para smoke test desde el admin. - UI nueva `admin-panel/src/features/platform/email-providers/` (rebuild del SPA incluido en `copiloto_core/admin/static/dist/`). - `copiloto_core/services/email.py` mantiene la API legacy (`get_email_provider`, `EmailMessage`, `EmailSendResult`, `EmailSendError`) como shim que delega al nuevo dispatcher — los callers (`services/invitations.py`) no requieren cambios. - Docs nueva `docs/EMAIL.md` con setup paso a paso por provider + troubleshooting + upgrade path. BREAKING CHANGES: - `RESEND_API_KEY` y `RESEND_API_KEY_FILE` ya NO se leen del env. - `Settings.resend_api_key` y `Settings.resend_api_key_file` eliminados. - Nuevo provider `app.email_providers` debe tener al menos una fila activa para enviar emails reales — si está vacía, el shim cae a NoopProvider (mismo comportamiento legacy que sin `RESEND_API_KEY`). Upgrade path: 1. Correr `python -m copiloto_core bootstrap` (aplica `30-email-providers.sql` idempotentemente). 2. Login al admin como platform_owner → `/admin/platform/email-providers`. 3. Crear el provider Resend (o el que prefieras) con la API key. 4. (Opcional) Agregar un segundo provider de respaldo con menor prioridad para fallback automático. Suite: 1279 passed, 3 skipped (baseline era 1242 — +37 tests nuevos cubren los 4 adapters + dispatcher + factory + CRUD). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ResendProviderhardcodeado por un subsistema multi-provider donde el platform_owner configura uno o más providers (Resend, SendGrid, Mailgun, SMTP) desde la admin SPA en/admin/platform/email-providers.app.email_dispatch_log.app.email_providers+ migration30-email-providers.sql(idempotente, RLS platform-owner-only).ResendProvider,SendGridProvider,MailgunProvider,SMTPProvider. Cada uno valida suconfig_jsonbcon Pydantic al instanciar y mapea errores HTTP/SDK a excepciones tipadas que el dispatcher inspecciona.copiloto_core.services.email) sigue funcionando:get_email_provider()ahora devuelve un wrapper sobre el nuevoEmailDispatcher. Si no hay providers configurados, cae aNoopProvider(mismo comportamiento legacy).BREAKING CHANGES
RESEND_API_KEY/RESEND_API_KEY_FILEenv vars ya no se leen.Settings.resend_api_keyySettings.resend_api_key_fileremovidos./admin/platform/email-providersy crear al menos un provider con su API key. Si la tabla está vacía → NoopProvider (loguea pero no envía), mismo comportamiento legacy.30-email-providers.sqlno se aplicó, el shim logea warning y cae a Noop sin crashear.Upgrade path
python -m copiloto_core bootstrapaplica30-email-providers.sqlidempotentemente (incluido en_PLATFORM_SQL_FILES)./admin/platform/email-providers, click "Añadir provider".Test plan
EmailProvidersContainer-*.js(~12 kB gz) copiado acopiloto_core/admin/static/dist/.app.email_dispatch_log.Files
copiloto_core/platform_schema/30-email-providers.sqlcopiloto_core/bootstrap.py(extiende_PLATFORM_SQL_FILES)copiloto_core/email/providers/{base,resend,sendgrid,mailgun,smtp,factory}.pycopiloto_core/email/dispatcher.pycopiloto_core/platform_admin/email_provider_routes.pycopiloto_core/services/email.py(refactor a wrapper sobreEmailDispatcher)admin-panel/src/features/platform/email-providers/docs/EMAIL.md(nuevo) + README + CONSUMER_ROUTEStests/test_unit_email_*.py(7 archivos, 37 tests)🤖 Generated with Claude Code