# Email from the dashboard The dashboard sends two mails: a **welcome invite** when an admin adds a user or presses "Send welcome email", and a **password reset** when someone uses "Forgot password?" on the sign-in page. Both go out through **Resend** — one HTTP POST to `https://api.resend.com/emails`, no SDK and no new dependency. The key stays on the server; the browser never sees it. ## Environment variables Set these in the Next.js server environment (Coolify env vars, `.env.local`, or whatever your host uses): | Variable | Required | What it does | | --- | --- | --- | | `RESEND_API_KEY` | **Yes** | The Resend API key (`re_…`). Without it the mail feature is off and the admin button answers "Email is not set up yet". | | `EMAIL_FROM` | No | The sender shown on the mail. Defaults to `MEGA Dashboard `. | | `EMAIL_REPLY_TO` | No | The address a reply goes to. Falls back to the sender. | | `APP_URL` | No | The dashboard's public URL, used to build the link in the mail and to point the logo at your own server. Falls back to the request's own origin. | `RESEND_API_KEY` is the **same key** you already use as the PocketBase SMTP password. Resend's "API key" and its "SMTP password" are different strings — use the API key here (`re_…`), not the SMTP password. ``` RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx EMAIL_FROM=MEGA Dashboard EMAIL_REPLY_TO=support@apphub.co.za APP_URL=https://wfdash.mega.infra.apphub.co.za ``` `APP_URL` must be the address people actually open. If it is wrong, every "Choose your password" link points somewhere nobody can reach. ## Run the migration The links live in a PocketBase collection. Run this once against the environment you are deploying to: ``` PB_URL=https://pb.mega.infra.apphub.co.za \ PB_ADMIN_EMAIL=admin@mega.co.za \ PB_ADMIN_PASSWORD=... \ python3 scripts/migrations/2026-11-02-password-links.py ``` The script is idempotent — it creates `password_links` when it is missing and verifies it when it is there. Every API rule on the collection stays `null`, so only the server's admin client can read or write these rows. The collection holds no passwords and no live links: only the SHA-256 hash of each token. A row lifted from the database cannot be turned back into a working link. ## Moving to megainfra.co.za later Until the domain is verified, mail from `support@apphub.co.za` may land in spam or be rejected. To send as your own domain: 1. **Verify the domain in Resend.** Resend → Domains → Add Domain. Use `megainfra.co.za`, or a sub-domain such as `mail.megainfra.co.za` if the root domain is used for something else. 2. **Add the DNS records** Resend shows you (SPF, DKIM, and a DMARC record if offered) to the DNS zone for the domain. Wait until Resend marks the domain as verified — usually minutes, sometimes longer if the zone is slow. 3. **Change `EMAIL_FROM`** to the verified sender, for example `MEGA Dashboard `, and restart the app. Keep `EMAIL_FROM` on a domain you have actually verified. Sending from an unverified domain is the usual reason mail disappears. ## What the user sees - **Welcome** — a greeting, what the dashboard (or the clock app, for a clock user) is for, a "Choose your password" button, three short getting-started steps, and the plain link for mail clients that will not show a button. The link is valid for **7 days**. - **Reset** — a "Reset password" button, valid for **1 hour**, and a line telling them to ignore the mail if they did not ask for it. Both pages also work by hand: `/forgot-password` asks for the email and `/set-password?token=…` sets the new password. The forgot-password route always answers the same way, so it never reveals whether an address has an account.