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

81 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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`](../../config/mail.php) e chave em [`config/services.php`](../../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.php``RESEND_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)_