# Email Provider Setup

FXT CRM Mailer sends every campaign email individually through a transactional email
provider API. **Never use the Namecheap mailbox SMTP for bulk campaigns** — it will get
the whole hosting account rate-limited and blacklisted.

Recommended: **Amazon SES** (cheapest at volume) or **Brevo** (easiest to start).

Credentials can go in `.env` **or** in *Settings → Email Provider* (stored encrypted in
the database; values saved in Settings override `.env`).

---

## DNS records first (required for any provider)

Add these in your DNS host for `fxtltd.co.uk` (Namecheap → Domain List → Advanced DNS,
or cPanel → Zone Editor if DNS is on the hosting server):

### SPF (TXT on the sending domain)

One SPF record only — merge providers into a single record:

```text
Host: @        Type: TXT
v=spf1 include:amazonses.com ~all                      # SES
v=spf1 include:spf.brevo.com ~all                      # Brevo
v=spf1 include:mailgun.org ~all                        # Mailgun
v=spf1 include:amazonses.com include:spf.brevo.com ~all  # multiple
```

### DKIM

Created per provider (below). Always enable it — DKIM is now mandatory for Gmail/Yahoo
bulk senders.

### DMARC (TXT)

```text
Host: _dmarc    Type: TXT
v=DMARC1; p=none; rua=mailto:dmarc-reports@fxtltd.co.uk; fo=1
```

Start with `p=none` (monitor), move to `p=quarantine` after a few clean weeks.

### Gmail/Yahoo bulk-sender requirements (2024+)

- SPF **and** DKIM aligned with the From: domain
- One-click unsubscribe headers — ✅ sent automatically by this system
- Complaint rate < 0.3% — ✅ the system auto-pauses at this threshold

---

## Amazon SES (preferred)

1. AWS Console → SES → choose region `eu-west-2` (London).
2. **Verified identities → Create identity → Domain** `fxtltd.co.uk`
   → enable *Easy DKIM* → add the 3 CNAME records shown to DNS → wait for *Verified*.
3. **Request production access** (SES starts in sandbox: only verified recipients).
   Account dashboard → Request production access; describe lawful B2B mailing with
   unsubscribe + suppression handling.
4. IAM → Users → create `fxt-crm-mailer` → attach inline policy:

```json
{
  "Version": "2012-10-17",
  "Statement": [{ "Effect": "Allow", "Action": ["ses:SendEmail"], "Resource": "*" }]
}
```

5. Create an **access key** → put in `.env` (`FXT_SES_KEY`, `FXT_SES_SECRET`,
   `FXT_SES_REGION=eu-west-2`) or Settings → Email Provider.
6. Set `FXT_MAIL_PROVIDER=ses`.

### SES webhooks (bounces/complaints/deliveries)

1. SNS → **Create topic** (Standard) `fxt-crm-events`, same region.
2. Topic → **Create subscription** → Protocol *HTTPS* → Endpoint:
   `https://crm.fxtltd.co.uk/webhooks/ses/YOUR_FXT_WEBHOOK_TOKEN`
   (the exact URL is shown on *Settings → Email Provider*).
   The app confirms the subscription automatically (SNS signature is verified
   cryptographically first).
3. SES → **Configuration sets → Create** `fxt-crm` → *Event destinations* → add SNS
   destination → events: **Send, Delivery, Bounce, Complaint, Reject, Delivery delay**
   (+ Open/Click if you want provider-side tracking too) → topic `fxt-crm-events`.
4. SES → Verified identity `fxtltd.co.uk` → assign default configuration set `fxt-crm`.

SES sandbox limits: 200/day. After production access, typically 50k/day — still keep the
app's own limits conservative (see *Settings → Sending limits*).

## Brevo

1. brevo.com → SMTP & API → **Generate API key** → `FXT_BREVO_KEY` / Settings.
2. Senders & Domains → **Domains** → add `fxtltd.co.uk` → add the DKIM + brevo-code TXT
   records → authenticate.
3. Webhooks → **Transactional** → Add webhook → URL:
   `https://crm.fxtltd.co.uk/webhooks/brevo/YOUR_FXT_WEBHOOK_TOKEN`
   → select: delivered, hard bounce, soft bounce, invalid email, spam (complaint),
   blocked, opened, clicked.
4. Set `FXT_MAIL_PROVIDER=brevo`.

## Mailgun

1. Add domain `mg.fxtltd.co.uk` (EU region recommended) → add the SPF/DKIM records shown.
2. API key → `FXT_MAILGUN_KEY`; domain → `FXT_MAILGUN_DOMAIN=mg.fxtltd.co.uk`;
   `FXT_MAILGUN_REGION=eu`.
3. Webhooks (per domain): set all event URLs to
   `https://crm.fxtltd.co.uk/webhooks/mailgun/YOUR_FXT_WEBHOOK_TOKEN`
   → copy the *HTTP webhook signing key* into Settings → Email Provider → "Webhook
   signing key" (signatures are verified).

## Resend

1. resend.com → Domains → add + verify `fxtltd.co.uk` (SPF + DKIM records shown).
2. API key → `FXT_RESEND_KEY`.
3. Webhooks → add endpoint
   `https://crm.fxtltd.co.uk/webhooks/resend/YOUR_FXT_WEBHOOK_TOKEN`
   → events: sent, delivered, bounced, complained, delivery_delayed, opened, clicked.

## Postmark

1. Postmark approval is strict — describe lawful opt-in/legitimate-interest B2B mail.
2. Sender Signatures → verify domain (DKIM + Return-Path CNAME).
3. Server → API token → `FXT_POSTMARK_TOKEN`. The app sends on the **broadcast** stream.
4. Server → Webhooks → add
   `https://crm.fxtltd.co.uk/webhooks/postmark/YOUR_FXT_WEBHOOK_TOKEN`
   → Delivery, Bounce, Spam complaint, Open, Click.

## SMTP (small tests only)

Settings → Email Provider → SMTP. Hard-limited use case: test emails to yourself.
The provider dropdown labels it "testing only" for a reason — do not run campaigns on it.

---

## Webhook security model

- URLs contain a 64-char secret token (`FXT_WEBHOOK_TOKEN`) — requests with a wrong token
  get 404.
- SES: full SNS signature verification (certificate chain, amazonaws host pinning).
- Mailgun: HMAC-SHA256 signature verification.
- All: rate-limited, idempotent (replayed payloads are detected by hash and ignored),
  raw payloads stored for audit in `provider_webhooks`.

## Testing the pipeline

1. Settings → Email Provider → save credentials.
2. Templates → any template → **Send test** to your own address → check inbox + headers
   (DKIM `PASS`, SPF `PASS`, `List-Unsubscribe` present).
3. Send a test campaign to a list containing only yourself.
4. Bounce test: include `bounce@simulator.amazonses.com` (SES) in a test list — the
   address must appear under **Suppression** with reason *hard bounce* within a minute.
5. Complaint test (SES): `complaint@simulator.amazonses.com` → suppressed with reason
   *complaint*.
