",
+ },
+ { env, transport },
+ ),
+ (error: unknown) =>
+ error instanceof Error &&
+ error.message === "Gmail email send failed" &&
+ !error.message.includes(secret),
+ );
+});
+```
+
+The injected `MailTransport` keeps the tests independent of Gmail and prevents tests from sending real mail.
+
+- [ ] **Step 2: Run the tests and verify they fail for the intended reason**
+
+Run:
+
+```bash
+pnpm test
+```
+
+Expected: FAIL because `lib/email.ts` does not exist yet. Do not add a mock module or skip the tests; Task 3 supplies the implementation.
+
+- [ ] **Step 3: Commit the failing tests**
+
+```bash
+git add lib/email.test.ts
+git commit -m "test: define Gmail mailer behavior"
+```
+
+---
+
+### Task 3: Implement the shared Gmail mailer
+
+**Files:**
+- Create: `lib/email.ts`
+- Test: `lib/email.test.ts`
+
+- [ ] **Step 1: Implement configuration validation and SMTP transport**
+
+Create `lib/email.ts` with this complete implementation:
+
+```ts
+import nodemailer, { type SendMailOptions } from "nodemailer";
+
+export type EmailAttachment = {
+ filename: string;
+ content: Buffer;
+};
+
+export type EmailMessage = {
+ to: string;
+ subject: string;
+ html: string;
+ attachments?: EmailAttachment[];
+};
+
+export type MailTransport = {
+ sendMail(message: SendMailOptions): Promise;
+};
+
+type EmailEnvironment = {
+ GMAIL_SMTP_USER?: string;
+ GMAIL_SMTP_APP_PASSWORD?: string;
+};
+
+export function getGmailCredentials(env: EmailEnvironment = process.env) {
+ const user = env.GMAIL_SMTP_USER?.trim();
+ const appPassword = env.GMAIL_SMTP_APP_PASSWORD?.trim();
+
+ if (!user || !appPassword) {
+ throw new Error("Gmail email credentials are not configured");
+ }
+
+ return { user, appPassword };
+}
+
+export function createGmailTransport(env: EmailEnvironment = process.env): MailTransport {
+ const { user, appPassword } = getGmailCredentials(env);
+
+ return nodemailer.createTransport({
+ host: "smtp.gmail.com",
+ port: 587,
+ secure: false,
+ requireTLS: true,
+ auth: {
+ user,
+ pass: appPassword,
+ },
+ });
+}
+
+export async function sendEmail(
+ message: EmailMessage,
+ options: { env?: EmailEnvironment; transport?: MailTransport } = {},
+): Promise {
+ const env = options.env ?? process.env;
+ const { user } = getGmailCredentials(env);
+ const transport = options.transport ?? createGmailTransport(env);
+
+ try {
+ await transport.sendMail({
+ from: user,
+ to: message.to,
+ subject: message.subject,
+ html: message.html,
+ attachments: message.attachments,
+ });
+ } catch {
+ throw new Error("Gmail email send failed");
+ }
+}
+```
+
+The helper fixes the sender to the authenticated Gmail user, enables STARTTLS on port 587, does not accept CC values, and never propagates raw provider error text.
+
+- [ ] **Step 2: Run the focused tests**
+
+Run:
+
+```bash
+pnpm test
+```
+
+Expected: all three tests PASS.
+
+- [ ] **Step 3: Run TypeScript validation**
+
+Run:
+
+```bash
+pnpm exec tsc --noEmit
+```
+
+Expected: TypeScript exits successfully. If Nodemailer's type declarations require a different import form, adjust only the import/types in `lib/email.ts` and rerun the focused tests.
+
+- [ ] **Step 4: Commit the mailer implementation**
+
+```bash
+git add lib/email.ts lib/email.test.ts
+git commit -m "feat: add Gmail SMTP mailer"
+```
+
+---
+
+### Task 4: Migrate statement and test-email actions
+
+**Files:**
+- Modify: `actions/statements.ts:1-5,107-138`
+- Modify: `actions/settings.ts:1-70`
+
+- [ ] **Step 1: Replace the provider import in the statement action**
+
+In `actions/statements.ts`, remove:
+
+```ts
+import { Resend } from "resend";
+```
+
+Add:
+
+```ts
+import { sendEmail } from "@/lib/email";
+```
+
+- [ ] **Step 2: Replace the statement Resend call**
+
+Replace the existing block from `// Email via Resend` through the `if (result.error)` return with:
+
+```ts
+ // Email via Gmail SMTP
+ await sendEmail({
+ to: parsed.data.recipient_email,
+ subject: `Delivery Statement — ${cust.name} (${start} to ${end})`,
+ html: `
Hi ${cust.name},
+
Please find your delivery statement attached for the period ${start} to ${end}.
+
Total litres delivered: ${totalQty.toFixed(2)} L
+Total value: R ${totalValue.toFixed(2)}
+
Thank you for your business.
+
— RJ Bulk Supplies
`,
+ attachments: [
+ {
+ filename: `delivery_statement_${cust.name.replace(/\s+/g, "_")}_${start.replace(/-/g, "")}.pdf`,
+ content: Buffer.from(pdfBytes),
+ },
+ ],
+ });
+```
+
+Do not add a `cc` property. Leave the existing `statement_log` creation immediately after this block so it still runs only after `sendEmail` resolves.
+
+- [ ] **Step 3: Replace the test-email provider call**
+
+In `actions/settings.ts`, remove:
+
+```ts
+import { Resend } from "resend";
+```
+
+Add:
+
+```ts
+import { sendEmail } from "@/lib/email";
+```
+
+Replace the body of `sendTestEmail` with:
+
+```ts
+export async function sendTestEmail(to: string): Promise {
+ if (!to) return { ok: false, error: "Recipient email required" };
+
+ try {
+ await sendEmail({
+ to,
+ subject: "RJ Bulk test email",
+ html: "
This is a test email from your RJ Bulk back-office. If you got this, email is working correctly.
",
+ });
+ } catch (err: any) {
+ return { ok: false, error: err?.message || "Send failed" };
+ }
+
+ return { ok: true };
+}
+```
+
+- [ ] **Step 4: Remove the obsolete email-settings action code**
+
+Delete `emailSettingsSchema`, `clean`, and `updateEmailSettings` from `actions/settings.ts`. Remove now-unused imports `pbCreate`, `pbList`, and `pbUpdate`; keep `revalidatePath`, `z`, `PocketBase`, `getAuth`, `pbId` only if they are still used by the remaining user actions, and keep `PB_URL` for PocketBase admin operations.
+
+- [ ] **Step 5: Run focused verification for the action migration**
+
+Run:
+
+```bash
+pnpm exec tsc --noEmit
+```
+
+Expected: no TypeScript errors. The existing statement code still owns PDF generation and `statement_log`; only the provider call changed.
+
+- [ ] **Step 6: Commit the action migration**
+
+```bash
+git add actions/statements.ts actions/settings.ts
+git commit -m "feat: send statements through Gmail SMTP"
+```
+
+---
+
+### Task 5: Simplify the Settings email panel
+
+**Files:**
+- Modify: `components/forms/settings-forms.tsx:31-163`
+
+- [ ] **Step 1: Remove the old email-settings state and form wiring**
+
+Delete these declarations from `SettingsForms`:
+
+```ts
+const [emailState, emailAction, emailPending] = useActionState(updateEmailSettings, initial);
+const emailFormRef = useRef(null);
+```
+
+Delete the `useEffect` that watches `emailState`, and change:
+
+```ts
+const [testEmail, setTestEmail] = useState(settings?.statement_from_email || "");
+```
+
+to:
+
+```ts
+const [testEmail, setTestEmail] = useState("");
+```
+
+Remove `updateEmailSettings` from the settings action import. Keep `sendTestEmail`, `addUser`, `deleteUser`, `ActionResult`, and the existing hooks required by the user dialog and other settings controls.
+
+- [ ] **Step 2: Replace the SMTP/Resend fields with a read-only Gmail panel**
+
+Replace the existing email `` from `` before `Email settings` through its closing `` with:
+
+```tsx
+
+
+ Email delivery
+ Customer statements are sent through Gmail SMTP.
+
+
+
+
Configured sender
+
rjbulk01@gmail.com
+
+ Gmail credentials are managed by the deployment environment and are not stored in PocketBase.
+
+
+
+
+
+ setTestEmail(e.target.value)} />
+
+
+
+
+
+```
+
+This removes sender, CC, fallback, and password inputs while retaining the test-email flow.
+
+- [ ] **Step 3: Run the settings type check**
+
+Run:
+
+```bash
+pnpm exec tsc --noEmit
+```
+
+Expected: no references remain to `emailAction`, `emailPending`, `emailFormRef`, or `updateEmailSettings` in the component.
+
+- [ ] **Step 4: Commit the Settings cleanup**
+
+```bash
+git add components/forms/settings-forms.tsx
+git commit -m "ui: simplify Gmail delivery settings"
+```
+
+---
+
+### Task 6: Remove Resend and update project documentation
+
+**Files:**
+- Modify: `package.json`
+- Modify: `pnpm-lock.yaml`
+- Modify: `AGENTS.md:19,72-74`
+- Modify: `Dockerfile:4-6`
+
+- [ ] **Step 1: Remove the unused Resend dependency**
+
+Run:
+
+```bash
+pnpm remove resend
+```
+
+Expected: `resend` is removed from `package.json` and `pnpm-lock.yaml`.
+
+- [ ] **Step 2: Update the stack and email documentation**
+
+Change the stack bullet in `AGENTS.md` from:
+
+```md
+- **Resend** for transactional email
+```
+
+to:
+
+```md
+- **Gmail SMTP via Nodemailer** for transactional email
+```
+
+Replace the email section with:
+
+```md
+## Email (Gmail SMTP)
+- Set `GMAIL_SMTP_USER` to `rjbulk01@gmail.com` and `GMAIL_SMTP_APP_PASSWORD` to a Google app password in `.env.local` or the production environment.
+- Gmail SMTP uses `smtp.gmail.com` on port 587 with STARTTLS.
+- The app sends statements and test emails from `rjbulk01@gmail.com` and does not use a default CC.
+- Without the Gmail variables, statement sending and test-email sending fail with a clear error; statement preview still works.
+```
+
+Do not include an actual app-password value.
+
+- [ ] **Step 3: Update Docker runtime guidance**
+
+Change the required environment comment in `Dockerfile` from:
+
+```dockerfile
+# POCKETBASE_URL, RESEND_API_KEY, RESEND_FROM
+```
+
+to:
+
+```dockerfile
+# POCKETBASE_URL, GMAIL_SMTP_USER, GMAIL_SMTP_APP_PASSWORD
+```
+
+- [ ] **Step 4: Check active code and docs for stale provider references**
+
+Run:
+
+```bash
+grep -RInE 'Resend|resend|RESEND_API_KEY|RESEND_FROM' \
+ actions components lib AGENTS.md Dockerfile package.json \
+ --exclude='*.test.ts' || true
+```
+
+Expected: no output. References in the approved migration design under `docs/superpowers/` are historical documentation and do not count as active provider references.
+
+- [ ] **Step 5: Commit provider cleanup and documentation**
+
+```bash
+git add package.json pnpm-lock.yaml AGENTS.md Dockerfile
+git commit -m "docs: document Gmail SMTP configuration"
+```
+
+---
+
+### Task 7: Run automated verification
+
+**Files:**
+- No source changes expected.
+
+- [ ] **Step 1: Run the focused mailer tests**
+
+```bash
+pnpm test
+```
+
+Expected: all three mailer tests pass.
+
+- [ ] **Step 2: Run lint**
+
+```bash
+pnpm lint
+```
+
+Expected: ESLint exits with status 0. Fix only migration-related unused imports or formatting issues.
+
+- [ ] **Step 3: Run a strict TypeScript check**
+
+```bash
+pnpm exec tsc --noEmit
+```
+
+Expected: no type errors.
+
+- [ ] **Step 4: Run the production build**
+
+```bash
+pnpm build
+```
+
+Expected: the Next.js production build completes successfully. No email is sent during the build because mail actions execute only when invoked by an authenticated request.
+
+- [ ] **Step 5: Check the final working tree and diff**
+
+```bash
+git status --short
+git diff origin/main...HEAD --check
+```
+
+Expected: only the intended Gmail migration commits/files are present, and `git diff --check` produces no output.
+
+---
+
+### Task 8: Configure Gmail and perform manual delivery verification
+
+**Files:**
+- Local `.env.local` or the deployment provider's secret environment configuration; never commit this file.
+
+- [ ] **Step 1: Revoke the previously exposed app password**
+
+In the Google account security settings for `rjbulk01@gmail.com`, revoke the app password that was previously pasted into chat. Do not reuse it.
+
+- [ ] **Step 2: Create a fresh Google app password**
+
+Enable two-step verification if necessary, then create a new app password for the RJ Bulk application. Keep the value private.
+
+- [ ] **Step 3: Configure the application environment**
+
+Set these values in the local/deployment environment:
+
+```text
+GMAIL_SMTP_USER=rjbulk01@gmail.com
+GMAIL_SMTP_APP_PASSWORD=(the newly generated private Google app password)
+```
+
+Do not paste the value into source files, PocketBase, or chat. Restart the development server or redeploy after changing environment variables.
+
+- [ ] **Step 4: Send a test email from Settings**
+
+Open Settings, enter a mailbox that can be checked, and click `Send test`.
+
+Expected: the recipient receives the test email from `rjbulk01@gmail.com`. If it fails, the UI returns a safe Gmail configuration/send error and does not expose the app password.
+
+- [ ] **Step 5: Send a real statement**
+
+Use Send Statement with a customer and date range containing delivery notes.
+
+Expected: the recipient receives the existing HTML message with the generated PDF attached, the sender is `rjbulk01@gmail.com`, no CC is added, and the Statements log records `Email Sent`.
+
+- [ ] **Step 6: Verify preview independence**
+
+Use the statement preview without Gmail variables present in a separate environment or after temporarily removing them.
+
+Expected: preview still renders because PDF generation does not depend on the email provider.
+
+---
+
+## Self-review checklist
+
+- **Spec coverage:** Gmail SMTP/Nodemailer architecture is covered by Tasks 1–3; action migration by Task 4; Settings cleanup by Task 5; Resend removal and docs by Task 6; automated checks by Task 7; secure credentials and real delivery by Task 8.
+- **Security coverage:** The plan never requests, stores, or commits an actual app password; sender is fixed to the authenticated user; raw transport errors are hidden.
+- **Type consistency:** `MailTransport`, `EmailMessage`, `getGmailCredentials`, `createGmailTransport`, and `sendEmail` are defined in Task 3 and used consistently in Tasks 2 and 4.
+- **No unresolved placeholders:** The only parenthesized environment text is an explicit instruction that the secret belongs outside the repository; no task relies on an unspecified implementation or test.
+- **Scope:** This is one provider migration subsystem with a shared helper, two callers, UI cleanup, dependency/documentation updates, and manual verification.