Files
amare/openspec/changes/add-production-providers/design.md
Manoel Freitas cafb1167ac feat: wire Resend mail and Cloudflare R2 storage (#4)
Add production provider deps/config so transactional email and CMS
media can use Resend and a dedicated r2 disk with custom-domain URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 21:00:16 -03:00

4.5 KiB
Raw Blame History

Context

Fase 01 entregaram CMS com uploads de mídia via PublicImageUploadRules, que hoje seleciona disco public localmente e s3 quando FILESYSTEM_DISK=s3. Laravel 13 já inclui mailer resend em config/mail.php e chave em config/services.php, mas faltam dependências (resend/resend-php, league/flysystem-aws-s3-v3) e configuração explícita para Cloudflare R2.

Produção usará Resend para e-mail transacional e Cloudflare R2 para mídia pública via domínio customizado. Local e CI continuam com MAIL_MAILER=log/array e disco public.

Goals / Non-Goals

Goals:

  • Resend como transporte nativo Laravel em produção (MAIL_MAILER=resend, RESEND_API_KEY).
  • Disco dedicado r2 com variáveis explícitas (R2_*) e URL pública via R2_URL (domínio customizado).
  • Seleção de disco de mídia alinhada a FILESYSTEM_DISK=r2.
  • Documentação de env vars e testes automatizados de config/seleção (sem chamadas live).

Non-Goals:

  • Templates de e-mail, filas de notificação de leads, ou fluxos CRM.
  • Buckets privados, signed URLs, ou automação de DNS/CDN.
  • Mudança de queue driver ou provisionamento Cloudflare via código.

Decisions

1. Resend via transporte nativo Laravel

Decisão: usar mailer resend já presente em config/mail.php + pacote resend/resend-php. Credencial em config/services.phpRESEND_API_KEY.

Alternativa rejeitada: SMTP genérico — menos idiomático; perde integração nativa Laravel 13.

2. Disco dedicado r2 (não reutilizar nome s3)

Decisão: adicionar disco r2 em config/filesystems.php com driver s3, endpoint R2 (https://<account_id>.r2.cloudflarestorage.com), use_path_style_endpoint=true, bucket e credenciais via R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, R2_ENDPOINT, R2_URL (domínio público customizado).

Alternativa rejeitada: reutilizar disco s3 com vars AWS_* — funciona tecnicamente, mas obscurece provedor real e conflita com futuro uso de AWS S3.

3. FILESYSTEM_DISK=r2 em produção

Decisão: default disk de produção = r2. Local/test = local ou public. PublicImageUploadRules::disk() retorna r2 quando default é r2, s3 quando default é s3, senão public.

Alternativa rejeitada: mapear r2 para disco s3 internamente — confunde operadores e quebra intenção explícita do env.

4. URLs públicas via domínio customizado

Decisão: R2_URL aponta para domínio customizado (ex.: https://media.example.com) mapeado ao bucket via Cloudflare. Disco r2 define url => env('R2_URL') e visibility => public.

Alternativa rejeitada: URL r2.dev gerenciada — menos controle de marca e SEO; usuário escolheu custom domain.

5. Testes sem integração live

Decisão: testes usam Mail::fake, Storage::fake('r2'), e asserts de config (config('mail.default'), config('filesystems.disks.r2'), PublicImageUploadRules::disk()). Nenhuma chamada HTTP a Resend/R2 no CI.

6. Defaults seguros por ambiente

Decisão:

Ambiente MAIL_MAILER FILESYSTEM_DISK Disco de upload CMS
Local dev log local public
Testes (phpunit) array (unset → local) public
Produção resend r2 r2

Risks / Trade-offs

  • [R2 credentials missing in prod] → app boot ok, upload fails at runtime; document required vars in .env.example and README; config test asserts disk definition exists.
  • [Custom domain not configured] → broken public image URLs; R2_URL documented as required for production media.
  • [Resend API key missing] → mail send fails; lead creation must not depend on mail (future phases per SPEC).
  • [Package version drift] → pin compatible versions in composer.json; run composer security-audit in CI.

Migration Plan

  1. Merge change; run composer install in production image build.
  2. Create R2 bucket, API token, and custom domain in Cloudflare dashboard (manual).
  3. Create Resend API key and verify sending domain.
  4. Set production env: MAIL_MAILER=resend, RESEND_API_KEY, FILESYSTEM_DISK=r2, R2_*, R2_URL, MAIL_FROM_*.
  5. Deploy; upload test image via Filament; verify public URL resolves.
  6. Rollback: revert env to MAIL_MAILER=log, FILESYSTEM_DISK=public; existing DB paths remain valid for local disk until re-upload.

Open Questions

  • (none — custom domain confirmed; Resend as sole production mail provider for this change)