# EMAIL_SETUP.md — TTEOTK Devotionals Marketing Stack

Operational runbook for getting the Email + SMS marketing platform online. Written
for a human operator (Geo). Ralph will not perform any of these steps.

Sending domain: **`mail.throughtheeyesoftheking.com`** (subdomain delegated to Resend).
Apex domain `throughtheeyesoftheking.com` keeps its existing receive/reply role.

---

## 1. Install dependencies (manual — run once)

The agent loop is forbidden from running `composer install` or `npm install`.
On the workstation (or first deploy):

```bash
composer install --no-interaction --prefer-dist --optimize-autoloader
npm install
npm run build
php artisan migrate --force
php artisan queue:table   # already created by 0001_01_01_000002_create_jobs_table
php artisan config:cache
```

New composer packages (Phase 0):

- `resend/resend-php` + `resend/resend-laravel` — ESP transport
- `mustache/mustache` — sandboxed personalization (never raw Blade with subscriber data)
- `tijsverkoyen/css-to-inline-styles` — server-side CSS inliner for compiled MJML
- `twilio/sdk` — SMS sending

New npm packages (Phase 0):

- `grapesjs` + `grapesjs-mjml` — drag-and-drop email editor
- `mjml-browser` — in-browser MJML → HTML compile
- `mustache` — client-side preview parity with server renderer
- `alpinejs` — popup + lightweight UI behaviors

---

## 2. `.env` keys to populate

Copy from `.env.example` and fill in:

```dotenv
MAIL_MAILER=resend
MAIL_FROM_ADDRESS="devotionals@mail.throughtheeyesoftheking.com"
MAIL_FROM_NAME="Devotionals"
MAIL_REPLY_TO="hello@throughtheeyesoftheking.com"

RESEND_API_KEY=re_...
RESEND_WEBHOOK_SECRET=whsec_...

ANTHROPIC_API_KEY=sk-ant-...
GEMINI_API_KEY=...
TWILIO_ACCOUNT_SID=AC...
TWILIO_AUTH_TOKEN=...
TWILIO_FROM_NUMBER=+1...
TWILIO_MESSAGING_SERVICE_SID=MG...
TWILIO_WEBHOOK_SECRET=...

QUEUE_CONNECTION=database
```

After editing: `php artisan config:clear`.

---

## 3. DNS records — Resend domain authentication

Add these to the DNS zone for `throughtheeyesoftheking.com`. Resend's
**Domains → Add Domain → mail.throughtheeyesoftheking.com** flow generates
the exact CNAME target hostnames; the values below are the canonical shape.

| Type  | Host (relative)                        | Value                                                             | Notes                          |
| ----- | -------------------------------------- | ----------------------------------------------------------------- | ------------------------------ |
| TXT   | `mail`                                 | `v=spf1 include:_spf.resend.com -all`                             | SPF for the sending subdomain  |
| CNAME | `resend._domainkey.mail`               | `resend._domainkey.<region>.resend.com.` (copy from Resend UI)    | DKIM key #1                    |
| CNAME | `resend2._domainkey.mail`              | `resend2._domainkey.<region>.resend.com.`                         | DKIM key #2 (rotation)         |
| CNAME | `resend3._domainkey.mail`              | `resend3._domainkey.<region>.resend.com.`                         | DKIM key #3 (rotation)         |
| MX    | `send.mail`                            | `feedback-smtp.<region>.amazonses.com.` (priority 10)             | Bounce/complaint routing       |
| TXT   | `_dmarc`                               | `v=DMARC1; p=none; rua=mailto:postmaster@throughtheeyesoftheking.com; ruf=mailto:postmaster@throughtheeyesoftheking.com; fo=1; aspf=s; adkim=s` | Apex DMARC, monitoring only    |

**DMARC ramp** (mandatory for Gmail/Yahoo 2024 bulk-sender compliance):

1. Week 1–2: `p=none` (collect aggregate reports, verify SPF/DKIM align)
2. Week 3–4: `p=quarantine; pct=25` → `pct=100`
3. Week 5+:  `p=reject; pct=100`

Verify each step with `dig +short TXT _dmarc.throughtheeyesoftheking.com` and
inspect the daily `rua` aggregate XML before tightening.

After DNS propagation (usually < 1 hour, allow 24h), click **Verify** in the
Resend dashboard. All four rows must show green.

---

## 4. Resend webhook

In Resend → **Webhooks → Add Endpoint**:

- URL: `https://throughtheeyesoftheking.com/webhooks/resend`
- Events: `email.sent`, `email.delivered`, `email.delivery_delayed`,
  `email.bounced`, `email.complained`, `email.opened`, `email.clicked`,
  `email.failed`
- Copy the **Signing Secret** → paste into `.env` as `RESEND_WEBHOOK_SECRET`.

The endpoint (Phase 3) verifies the `svix-signature` HMAC before mutating
state. Auto-pause triggers when the rolling complaint rate exceeds 0.3 %
(Gmail/Yahoo threshold).

---

## 5. Cron — drives both the scheduler and the queue worker

Shared cPanel hosts cannot run a persistent `queue:work` daemon, so we let
Laravel's scheduler invoke it once per minute with `--stop-when-empty`. Add
this single line in cPanel → **Cron Jobs**:

```cron
* * * * * cd /home/USERNAME/throughtheeyesoftheking.com && /usr/local/bin/php artisan schedule:run >> /dev/null 2>&1
```

What it does:

- `schedule:run` fires every minute.
- `routes/console.php` registers
  `Schedule::command('queue:work --stop-when-empty --max-time=55 --tries=3')->everyMinute()->withoutOverlapping(5)->runInBackground()`.
- The worker drains the `jobs` table and exits within 55 s (well inside the
  60 s cron tick). `withoutOverlapping(5)` releases the lock after 5 min in
  case a worker hangs.

Confirm `php -v` path with `which php` on the server and substitute as needed.

---

## 6. A2P 10DLC brand registration (Twilio SMS)

US carriers require every business sending application-to-person SMS to
register a **Brand** and at least one **Campaign**. This is the longest-lead
item in the project (3–6 weeks); start it the day Resend DNS is requested.

**Step-by-step (Twilio Console → Messaging → Regulatory Compliance → A2P 10DLC):**

1. **Create Brand** (Standard, not Sole-Proprietor — better throughput, lower fees)
   - Legal business name (must match EIN registration)
   - EIN / Tax ID
   - Business address, website (`https://throughtheeyesoftheking.com`)
   - Vertical: `RELIGION` (closest match for devotional content)
   - Stock symbol: leave blank (private)
   - Pay one-time $44 brand-vetting fee.
2. **Wait 1–7 days** for The Campaign Registry (TCR) to vet the brand. A
   "VERIFIED" status unlocks campaign creation.
3. **Create Campaign** under the verified brand:
   - Use case: `MIXED` (marketing + account notifications) — most flexible
   - Description: "Daily devotionals, account confirmations, and one-time
     promotional content for opted-in TTEOTK subscribers."
   - Sample messages (provide ≥ 2):
     - `TTEOTK: Today's devotional is live — "Walk by Faith". Read: https://...
       Reply STOP to opt out, HELP for help. Msg & data rates may apply.`
     - `TTEOTK: Confirm your subscription by replying YES. Reply STOP to opt
       out, HELP for help.`
   - Opt-in flow description: "Visitors enter their phone number on
     `/sms-signup` after checking a consent box that reads: 'I agree to
     receive recurring marketing and devotional SMS from TTEOTK. Msg & data
     rates may apply. Reply STOP to cancel, HELP for help.' Double opt-in
     confirmation SMS is sent before any further messages."
   - Opt-in keywords: `START`, `YES`, `UNSTOP`
   - Opt-out keywords: `STOP`, `STOPALL`, `UNSUBSCRIBE`, `CANCEL`, `END`, `QUIT`
   - Help keywords: `HELP`, `INFO`
   - Help message: `TTEOTK Devotionals support: hello@throughtheeyesoftheking.com.
     Reply STOP to unsubscribe. Msg & data rates may apply.`
   - Attach the public `/sms-signup` URL (must be live before submission).
   - Pay $10 + $1.50/mo per campaign.
4. **Wait 1–4 weeks** for carrier approval (T-Mobile is fastest, AT&T slowest).
5. **Create / link a Messaging Service** and assign the campaign to it. Copy
   `Messaging Service SID` → `.env` as `TWILIO_MESSAGING_SERVICE_SID`.
6. Provision a **toll-free or 10DLC long code** (or use a short code), assign
   it to the Messaging Service. Long codes typically deliver at 1 msg/sec per
   number — provision multiples if list > 50k.
7. Configure the inbound webhook on the Messaging Service:
   - URL: `https://throughtheeyesoftheking.com/webhooks/twilio/sms`
   - Method: `POST`
   - Verifies `X-Twilio-Signature` against `TWILIO_AUTH_TOKEN` (Phase 7).

**Pre-launch checklist before first marketing send:**

- [ ] Brand status = VERIFIED
- [ ] Campaign status = APPROVED on all three major carriers
- [ ] Messaging Service contains the campaign + a sending number
- [ ] Inbound webhook tested with `STOP` → suppression created
- [ ] Privacy policy on the website explicitly mentions SMS data sharing
      with Twilio (carriers spot-check this during vetting)

---

## 7. Verification

After all of the above:

```bash
php artisan tinker
>>> Mail::raw('Resend smoke test', fn($m) => $m->to('you@example.com')->subject('TTEOTK smoke'));
```

Then send a test SMS from the admin SMS console (Phase 7). Confirm:

- Resend dashboard shows the test email under **Emails** with `delivered`.
- DKIM column shows `pass`. SPF column shows `pass`. DMARC column shows `pass`.
- Twilio Console → Monitor → Messaging shows the SMS with `delivered` and
  no carrier-filtering errors.

You're ready for Phase 1.
