# Launch Checklist — TempMail Admin

Work through this top to bottom. Items marked **⚠ SECURITY** are things I
flagged during review that work as-shipped but should be addressed before
this is reachable by the public internet.

> This checklist assumes the **SSH + PM2** deployment path
> (`deploy/WHM_DEPLOY.md`). If you're deploying entirely through cPanel's web
> UI instead (no terminal — see `deploy/CPANEL_WEB_ONLY.md`), skip the PM2 /
> Apache-reverse-proxy-specific items below and follow that guide's steps
> instead; the database, install-wizard, and mail-provider sections still apply.

---

## 1. Server prerequisites (WHM/cPanel, root SSH)

- [ ] Root SSH access confirmed
- [ ] Node.js 20.x installed system-wide (`node -v`)
- [ ] `pm2` installed globally (`npm install -g pm2`)
- [ ] `openssl version` checked, and `binaryTargets` in `prisma/schema.prisma`
      trimmed/matched to it (currently set to cover both 1.0.x and 3.0.x —
      narrow this once you know which one you actually have)
- [ ] `mod_proxy` + `mod_proxy_http` enabled in EasyApache 4

## 2. Database

- [ ] MariaDB database + user created (via WHM MySQL Databases, or `mysql` CLI)
- [ ] Full cPanel-prefixed names captured (`cpaneluser_dbname`, `cpaneluser_dbuser`)
- [ ] Have host/port/database/username/password ready — you'll paste them
      into the `/install` wizard (or into `.env`'s `DATABASE_URL` for the
      manual path)

## 3. Build & start the app

- [ ] `npm install` (devDependencies included — do **not** use `--omit=dev`)
- [ ] `cp .env.example .env` — leave the placeholders as-is; the install
      wizard fills them in next
- [ ] `npm run build` completes with no errors
- [ ] `pm2 start ecosystem.config.js` — both `tempmail-web` and `tempmail-purge`
      show `online` in `pm2 status`
- [ ] `pm2 save` + `pm2 startup` run, so both processes survive a reboot
- [ ] `pm2 logs tempmail-web --lines 50` — no repeating errors on boot

## 4. Apache reverse proxy

- [ ] `deploy/apache-reverse-proxy.conf` copied into the persistent userdata
      include paths for both `std` and `ssl` (see comments at the top of that file)
- [ ] `apachectl -t` prints "Syntax OK" before reloading
- [ ] `/scripts/rebuildhttpdconf` + Apache restarted
- [ ] Visiting `https://yourdomain.com` loads the install wizard (not a 404
      or the default cPanel page)
- [ ] AutoSSL certificate is valid for the domain (padlock in browser)
- [ ] **⚠ SECURITY**: confirm port `3050` is not reachable from outside — from
      another machine, `curl http://yourdomain.com:3050` should time out /
      connection-refuse, not return the app

## 5. Run the install wizard — do this immediately

**⚠ SECURITY**: `/install` is reachable by anyone until this finishes — same
tradeoff WordPress's install wizard has. Complete this step right after step 4,
before telling anyone the domain is live.

- [ ] Visited the domain, landed on `/install` automatically
- [ ] Database step: entered connection details, "Test Connection" succeeded
- [ ] Admin account step: username/password set (saved somewhere safe — a
      password manager, not a text file on the server)
- [ ] Mail provider step: filled in now, or explicitly skipped for later (see step 6)
- [ ] "Install Now" completed successfully
- [ ] App restarted automatically (or you ran `pm2 restart ecosystem.config.js`
      manually if the wizard reported it couldn't find PM2)
- [ ] Redirected to `/login` and logged in successfully — confirms
      `DATABASE_URL`, `NEXTAUTH_SECRET`, and the admin account all work end-to-end
- [ ] Revisiting `/install` now redirects to `/login` (confirms it locked
      itself — `INSTALL_COMPLETE=true` in `.env`)

**Prefer no wizard?** Skip this section and instead: fill in `.env` by hand
(`DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET` via `openssl rand -base64 32`,
`CRON_SECRET` via `openssl rand -hex 32`), run
`npx prisma db push --schema=prisma/schema.prisma`, then `npm run seed`
(also sets `INSTALL_COMPLETE=true` for you), then restart PM2.

## 6. Mail provider config (if skipped during the wizard)

- [ ] `INBOUND_WEBHOOK_SIGNING_KEY` — copied from your inbound mail provider's dashboard
- [ ] `INBOUND_MX_HOST` / `INBOUND_MX_HOST_FALLBACK` — match what your provider tells customers to use
- [ ] Added directly to `.env`, then `pm2 restart ecosystem.config.js` to apply
- [ ] `.env` is **not** committed to git (already covered by `.gitignore` — just don't force-add it)

## 7. Domain + inbound mail provider

- [ ] Custom domain added in the dashboard's "Domains" view
- [ ] MX + TXT records added at the registrar, matching the "DNS Settings" modal exactly
- [ ] "Check DNS Now" in the modal returns verified (may take a few hours to propagate)
- [ ] Inbound webhook URL configured at the provider: `https://yourdomain.com/api/inbound-mail`
- [ ] Provider's webhook signing secret matches `INBOUND_WEBHOOK_SIGNING_KEY` exactly
- [ ] Sent a real test email to a created temp address and confirmed it shows
      up in the inbox viewer within a minute or two

## 8. Purge job verification

- [ ] Create a temp email, manually set its `expiresAt` a minute in the past
      directly in the DB, and confirm `tempmail-purge` deletes it within 15 minutes
      (`pm2 logs tempmail-purge` will print a line when it runs a cleanup)
- [ ] Confirm associated `InboundMessage` rows for that email were deleted too (cascade check)

---

## Known TODOs / not yet implemented

These are things that work as designed but are worth deciding on before
going fully live:

- [ ] **⚠ SECURITY — HTML sanitization**: `bodyHtml` from inbound messages is
      rendered via `dangerouslySetInnerHTML` in `components/InboxViewer.tsx`
      with no sanitization. A malicious sender could embed a `<script>` tag
      or tracking pixel. Recommended fix: add `sanitize-html` (or DOMPurify
      server-side) in `app/api/inbound-mail/route.ts` before the message is
      persisted, so sanitization happens once at ingestion rather than on
      every render. *(Say the word and I'll implement this.)*
- [ ] **Rate limiting**: `/api/inbound-mail` has no rate limit beyond
      signature verification — a valid signature only proves the sender is
      your mail provider, not that volume is bounded. Consider a basic
      per-domain/per-minute cap if abuse becomes a concern.
- [ ] **Multi-admin support**: `User` model supports multiple rows, but
      there's no UI/API route to create additional admins beyond the
      wizard/seed script. Fine for a single-operator dashboard; add a route
      if you need more.
- [ ] **Soft-delete vs hard-delete**: `TempEmail.isActive` exists in the
      schema but nothing currently flips it to `false` — deletion is always
      immediate/hard (via the DELETE routes or the purge job). This is fine
      as-is; just don't build features assuming soft-delete exists yet.
- [ ] **Webhook payload shape**: `app/api/inbound-mail/route.ts` assumes a
      Mailgun-style JSON payload (`recipient`, `sender`, `body-html`, etc.).
      If your provider is Postmark/SendGrid/etc., the field names and
      signature-composition (`timestamp + body` vs raw body only) will
      differ — check `lib/webhook-verify.ts`'s two exported functions and
      your provider's docs before going live.
- [ ] **Backups**: no backup strategy is set up for the MySQL database.
      WHM's own backup system (Backup Configuration) can cover this, or a
      simple `mysqldump` cron — up to you, not part of the app itself.

---

## Quick reference — common commands

```bash
# Redeploy after a code change
cd /home/myuser/apps/tempmail-app
git pull
npm install                    # only if package.json changed
# only if schema.prisma changed:
npx prisma db push --accept-data-loss --schema=prisma/schema.prisma
npm run build
pm2 restart ecosystem.config.js

# Check everything is alive
pm2 status
pm2 logs tempmail-web --lines 100
pm2 logs tempmail-purge --lines 100

# Force an immediate purge run (normally automatic every 15 min)
curl -X POST https://yourdomain.com/api/cron/purge \
  -H "Authorization: Bearer $CRON_SECRET"
```
