Send sign-in mail as HTML so quoted-printable wrapping cannot break the verify URL.
Staging was delivering magic links that looked expired because the token query string was encoded and wrapped in plaintext MIME. Also read rate-limit retry from the API error body, mention spam in the success copy, and document SES relay DNS. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -227,7 +227,7 @@ npm run dev
|
||||
|
||||
1. TLS certificates and hostnames. _On Cloudron: handled by the platform per chosen subdomain._
|
||||
2. PostgreSQL backups and restore drill. _On Cloudron: daily snapshots; configure retention in admin UI._
|
||||
3. SMTP DNS (SPF, DKIM). _On Cloudron: handled for the platform-managed domain._
|
||||
3. SMTP DNS (SPF, DKIM). _TLS for the app hostname is Cloudron/Let's Encrypt. Mail is SES-relayed: publish SES DKIM (and SPF `include:amazonses.com`) via Cloudron Domains → Namecheap. Cloudron skips SPF/DKIM checks when a relay is configured. See [`ops-runbook.md`](ops-runbook.md) §8.1._
|
||||
4. Health check URL for reverse proxy (`/api/health`). _On Cloudron: set `healthCheckPath` in `CloudronManifest.json`._
|
||||
5. Log retention and alerts for 5xx errors. _On Cloudron: app log viewer; export off-platform if longer retention is needed._
|
||||
|
||||
|
||||
@@ -101,8 +101,16 @@ per-app in the manifest and provisioned at install time.
|
||||
- Backups: Cloudron's automatic backups are already on for the host
|
||||
(legacy app shows weekly snapshots ~451 MB each). Same default
|
||||
applies to new apps.
|
||||
- TLS / DNS / SPF / DKIM: handled by Cloudron for any subdomain of
|
||||
`communityrule.info`.
|
||||
- TLS for Cloudron app hostnames: handled by Cloudron (Let's Encrypt).
|
||||
- **Mail DNS (SPF/DKIM):** *not* automatic for this domain. Cloudron's
|
||||
DNS provider for `communityrule.info` is Namecheap, but outbound mail
|
||||
is **Amazon SES relay**. Cloudron's own mail-status check **skips**
|
||||
SPF and DKIM and says to configure them on the relay. Add the SES
|
||||
identity's **DKIM CNAME** records (and optionally
|
||||
`include:amazonses.com` on SPF) in Cloudron → *Domains* →
|
||||
`communityrule.info` → DNS so they publish to Namecheap. See
|
||||
[`ops-runbook.md`](ops-runbook.md) §8.1. DMARC on the domain is
|
||||
currently `p=reject`.
|
||||
|
||||
## 5. Cutover plan (side-by-side, never in-place)
|
||||
|
||||
@@ -476,7 +484,7 @@ steps below are still required.
|
||||
| ------- | ------------ | ----- |
|
||||
| Image pull error on install | Repo still private, or wrong tag in manifest | §6.3; `docker pull --platform linux/amd64 …` from laptop |
|
||||
| Health `503` / `database: disconnected` | Postgres addon not provisioned or URL missing | Cloudron app → Environment; expect `CLOUDRON_POSTGRESQL_URL` |
|
||||
| Magic link not sent | Mail addon or `SMTP_FROM` | Cloudron mail logs; `CLOUDRON_MAIL_SMTP_*` vars |
|
||||
| Magic link not sent | Mail addon, `SMTP_FROM`, or SES DNS | Cloudron mail logs; `CLOUDRON_MAIL_SMTP_*`; [ops-runbook §8.1](ops-runbook.md#81-mail-dns-when-ses-is-the-relay) |
|
||||
| Upload `server_misconfigured` | `UPLOAD_ROOT` unset | Set to `/app/data/uploads` (§3) |
|
||||
| Container crash on start | Migration failure | App logs around `prisma migrate deploy` |
|
||||
| No "Recommended" on method cards | `MethodFacet` not seeded | §10 step 6; API should return `matches.score > 0` for some methods when `facet.*` set |
|
||||
|
||||
@@ -256,13 +256,29 @@ Full detail: [`ops-backend-deploy.md` §3](ops-backend-deploy.md#3-environment-v
|
||||
| Image pull error on update | Private repo, wrong tag, or amd64 manifest missing | Confirm repo is public; verify pull with `--platform linux/amd64` (§3.1) |
|
||||
| Health `503` / `database: disconnected` | Postgres addon or `CLOUDRON_POSTGRESQL_URL` missing | Cloudron app → Environment |
|
||||
| Container crash on start | Migration failure | App logs around `prisma migrate deploy`; fix forward with new migration |
|
||||
| Magic link not sent | Mail addon or `SMTP_FROM` | Cloudron mail logs; `CLOUDRON_MAIL_SMTP_*` vars |
|
||||
| Magic link not sent | Mail addon, `SMTP_FROM`, or SES DNS | Cloudron mail logs (`CLOUDRON_MAIL_SMTP_*`); inbox/spam; SPF/DKIM for SES (§8.1) |
|
||||
| Upload `server_misconfigured` | `UPLOAD_ROOT` unset | `cloudron env set --app <app> UPLOAD_ROOT=/app/data/uploads` |
|
||||
| No “Recommended” on method cards | Seed not run | §3.4 — `node prisma/seed.bundle.cjs` |
|
||||
| Rate limit too aggressive after deploy | Expected per §6.1 | Single instance only; limits reset on container restart |
|
||||
|
||||
App logs: Cloudron dashboard → *Logs* tab, or `cloudron logs --app <app> -f`.
|
||||
|
||||
### 8.1 Mail DNS when SES is the relay
|
||||
|
||||
`communityrule.info` outbound mail is **Amazon SES SMTP** (`email-smtp.us-east-2.amazonaws.com:587`), not Cloudron's own MTA. Cloudron Mail → domain status therefore **skips SPF and DKIM** ("configure the relay provider") and only checks MX, DMARC (`v=DMARC1; p=reject; pct=100`), and that the SES connection works.
|
||||
|
||||
That is expected. Recipients still authenticate the visible `From:` (`staging.app@communityrule.info` on staging) against **SES DKIM/SPF**, not `a:my.medlab.host`.
|
||||
|
||||
**Operator steps (AWS + Cloudron DNS, not app code):**
|
||||
|
||||
1. In **AWS SES** (us-east-2), open the verified identity for `communityrule.info` (create one if missing). Copy the **DKIM CNAME** records SES shows (three `*._domainkey.communityrule.info` names).
|
||||
2. In **Cloudron** → *Domains* → `communityrule.info` → DNS, add those CNAMEs. Cloudron's Namecheap provider publishes them to the registrar. Confirm with `dig +short CNAME <selector>._domainkey.communityrule.info`.
|
||||
3. Optional but recommended for SPF alignment: add `include:amazonses.com` to the existing TXT SPF, e.g. `v=spf1 include:amazonses.com a:my.medlab.host ~all`. Do not remove `a:my.medlab.host` until you know nothing still sends directly from the box.
|
||||
4. Leave DMARC at `p=reject` once DKIM verifies in SES; if a provider still quarantines after DKIM is live, inspect that provider's headers before relaxing DMARC.
|
||||
5. Retest: request a magic link to Gmail **and** a non-Gmail inbox (May First / university). Check spam. Staging From is `Community Rule <staging.app@communityrule.info>`.
|
||||
|
||||
`SMTP_FROM` should stay the Cloudron mailbox (`staging.app@communityrule.info` on staging, `hello@communityrule.info` on the apex app). The app falls back to `CLOUDRON_MAIL_FROM` if `SMTP_FROM` is unset.
|
||||
|
||||
## 9. Related docs
|
||||
|
||||
- [`ops-backend-deploy.md`](ops-backend-deploy.md) — first install, cutover
|
||||
|
||||
Reference in New Issue
Block a user