# Email / SMTP (Step 31)

This project sends transactional email with **nodemailer** directly (no SendGrid/Mailgun SDK). Auth OTP, welcome, password-reset, and ride/payment notification emails are best-effort and never run inside ride DB transactions.

## 1. cPanel / hosting SMTP

Typical cPanel mail settings:

| Setting | Common value |
|--------|----------------|
| Host | `mail.yourdomain.com` (or the host shown in cPanel → Email → Connect Devices) |
| Port 465 | SSL/TLS (`SMTP_SECURE=true`) |
| Port 587 | STARTTLS (`SMTP_SECURE=false`) |
| Username | Full mailbox address, e.g. `noreply@yourdomain.com` |
| Password | Mailbox password (store in secrets — never commit) |

Create a dedicated mailbox (e.g. `noreply@…`) rather than using a personal inbox.

## 2. Environment variables

Primary `SMTP_*` keys with optional legacy `MAIL_*` fallback:

```env
APP_NAME=Ride Hailing
SMTP_HOST=mail.example.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=noreply@example.com
SMTP_PASSWORD=replace-me
SMTP_FROM_EMAIL=noreply@example.com
SMTP_FROM_NAME=Ride Hailing

# Optional legacy aliases (used only when SMTP_* is empty)
MAIL_HOST=
MAIL_USER=
MAIL_PASSWORD=
MAIL_FROM=
```

Resolution rules (see `src/utilities/configuration.ts`):

- `SMTP_HOST` ← `SMTP_HOST` or `MAIL_HOST`
- `SMTP_PORT` ← `SMTP_PORT` or `465`
- `SMTP_SECURE` ← explicit `true`/`false`, else **true when port is 465**
- `SMTP_USER` / `SMTP_PASSWORD` ← SMTP or MAIL aliases
- `SMTP_FROM_EMAIL` ← `SMTP_FROM_EMAIL` or `MAIL_FROM` or `SMTP_USER`
- `SMTP_FROM_NAME` ← `SMTP_FROM_NAME` or `APP_NAME` or `Ride Hailing`

Email is treated as **configured** only when host, user, and password are all non-empty. Otherwise sends are skipped (`{ ok: false, skipped: true }`) without throwing.

Placeholders live in `test.env`, `production.env`, and `development.env`. Put real secrets only in the deploy environment / secret manager.

## 3. Local testing

1. Leave `SMTP_HOST` / `SMTP_USER` / `SMTP_PASSWORD` empty → app boots; emails are skipped; Auth still issues hashed OTP/reset in MySQL.
2. For a real mailbox test, set SMTP vars in a local env file (not committed) and call register / resend OTP / forgot password.
3. Unit tests **mock** `nodemailer.createTransport` — they never open a network socket.

Optional: use a catch-all test inbox or a local SMTP catcher; do not point CI at production SMTP.

## 4. Health check

`GET /health` includes:

```json
"email": { "configured": false, "status": "skipped" }
```

When configured, the app calls `transporter.verify()` once for status `up` \| `down`. A down SMTP **does not** fail overall app health (`status` stays `ok`). Credentials are never returned.

## 5. What is sent

| Flow | When | Notes |
|------|------|--------|
| Auth OTP | After customer/driver signup (post-commit for driver TX) and resend OTP | 6-digit code in email body only |
| Welcome | After successful OTP verify | Failure does not block JWT issue |
| Password reset | After forgot-password issues credential | Same 6-digit OTP-style model (no magic links) |
| Ride / payment | After `NotificationService.emitCreatedRealtime` (post-commit) | Skips if no email or `user.notification === false` |

## 6. Troubleshooting

| Symptom | Check |
|---------|--------|
| Emails skipped | `SMTP_HOST`, `SMTP_USER`, `SMTP_PASSWORD` all set? |
| Auth works but no mail | Health `email.status`; firewall/cPanel “SMTP authentication”; wrong port/secure combo |
| Port 587 fails | Set `SMTP_SECURE=false` |
| Port 465 fails | Set `SMTP_SECURE=true` |
| Logs show masked recipient | Expected (`ab***@domain.com`) — OTP/password never logged by EmailService |
| Ride TX rolled back | Email must not be inside TX — use post-commit emit only |

## 7. Security

- Never commit real SMTP passwords.
- Never return OTP / reset codes in API JSON.
- Never log OTP, reset codes, or SMTP passwords.
- No public “send arbitrary email” endpoint in Step 31.
