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>
This commit is contained in:
2026-07-29 21:00:16 -03:00
committed by GitHub
parent 3dc1f449ee
commit cafb1167ac
16 changed files with 838 additions and 5 deletions

View File

@@ -0,0 +1,80 @@
## 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)_