## Required

`RAILS_MASTER_KEY`, or `SECRET_KEY_BASE` if you prefer environment variables to the encrypted file.
Rails cannot decrypt credentials or sign cookies without one, so it raises during initialization and
the deploy fails on its health check.

```bash
bin/kamal secrets print
```

## Not a secret, but launch-blocking anyway

`APP_HOST`. Mailers and the SEO surfaces have no request to infer a hostname from, so they read this.
Without it, magic-code sign-in emails link to `localhost:3000` and your sitemap advertises localhost
URLs, both silently.

## Add when you need the thing

| Variable | Until you set it |
|---|---|
| `STRIPE_SECRET_KEY` | Checkout runs against the local fake and entitlement works. |
| `STRIPE_WEBHOOK_SECRET` | Needed as soon as the Stripe key is real. |
| `RESEND_API_KEY` and `MAIL_FROM` | Mail is not delivered. |
| `TURNSTILE_SITE_KEY` and `TURNSTILE_SECRET_KEY` | The bot check passes everything. |
| `S3_BUCKET` and the AWS pair | Uploads go to the mounted volume. |

Every row in that table is genuinely optional, which is the property the adapter and fake pattern
buys. `docs/BRING_YOUR_OWN_KEYS.md` is the full list with what each one unlocks.

## The order that avoids rework

1. Deploy with the master key only, and confirm the app is up.
2. Add `APP_HOST`, then send yourself a sign-in code and click the link.
3. Add Resend, and check a real inbox shows SPF, DKIM and DMARC passing.
4. Add Stripe in test mode, run a checkout, confirm the webhook lands.
5. Switch Stripe to live, and set the statement descriptor before the first real charge.

Each step is verifiable on its own, so a failure names its own cause. Doing all five at once means
debugging five integrations against one symptom.

## Confirm before you announce

```bash
bin/check
curl -sI https://yourdomain.com/up | head -1
```
