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>
81 lines
4.5 KiB
Markdown
81 lines
4.5 KiB
Markdown
## Context
|
||
|
||
Fase 0–1 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)_
|