# FCT v2.2.0 SMS template spec (`fct_v220_sms_spec`)

**Project:** FCT Command Center  
**Job:** `92b7964f-e8ad-421d-855a-fb27635ee05e` FCT v2.2.0 [3/3]  
**Owner / reviewer:** ChatGPT (templates + TCPA content review); Grok red-teams rendered length / spam risk  
**Date:** 2026-09-05  
**Status:** v1.1 — ChatGPT review + Grok red-team reconciled — **no live SMS send in this job**

---

## 1) Purpose

Three **transactional** driver SMS variants for French Camp Transport dispatch. Not marketing. Review measures **fully rendered** messages (variables filled), not placeholders alone.

This job covers **wording + render length**. Consent records, opt-out webhooks, quiet hours, and A2P registration live on job `69ea39bf` / ops — wording alone is **not** TCPA safe harbor.

---

## 2) Sender identity

| Field | Value |
| --- | --- |
| Brand / sender name | Short form `FCT Dispatch` (approved) |
| From number | Twilio toll-free or 10DLC once job `69ea39bf` Business Profile is approved |
| Business website | Custom domain required (not `tiiny.site`) — see Twilio job blocker |
| Business email | Matching domain mailbox (e.g. `dispatch@…`) |

Every template **must** identify the sender by brand name in the body.

---

## 3) Length + encoding contract (reconciled)

**Hard policy (job requirement wins over older soft 320 cap):**

1. Fully rendered message must be **single-segment GSM-7** (≤ 160 septets; prefer ≤ 150 for headroom).
2. **No UCS-2 / Unicode** in production sends: no arrows (`→`), typographic dashes (`–`/`—`), curly quotes, emoji, or non-GSM accented letters in templates or substituted values.
3. GSM extension characters (`^ { } \ [ ] ~ | €`) cost **2 septets** each — count them.
4. Character count &lt; 160 does **not** prove one segment if encoding is UCS-2 (Twilio: 70 / 67).
5. Before send: reject missing fields, unresolved `{{placeholders}}`, invalid event state, UCS-2 encoding, or &gt;1 segment. Show dispatch a reviewable correction — **never** silent truncate of location, timing, sender, or STOP text.
6. Multipart is **out of policy** for these three variants (spam-filter + cost risk).

Source: [Twilio SMS character limit](https://www.twilio.com/docs/glossary/what-sms-character-limit).

### Field caps (enforced at render)

| Field | Max chars | Notes |
| --- | --- | --- |
| `load_id` | 16 | Required |
| `pu_name` / `del_name` | 28 | Short facility label; truncate with review if longer |
| `pu_city` / `del_city` | 18 | |
| `pickup_window_tz` / `delivery_window_tz` | 28 | Must include timezone (`PT`) |
| `change_summary` | 90 | Must name changed action + new value + facility/tz |

---

## 4) Shared variables (v1.1)

| Variable | Meaning | Example |
| --- | --- | --- |
| `{{load_id}}` | Dispatch load id | `L-2403` |
| `{{pu_name}}` | Pickup facility short name | `Diamond Walnut` |
| `{{pu_city}}` | Pickup city | `Stockton` |
| `{{del_name}}` | Delivery facility short name | `Purina` |
| `{{del_city}}` | Delivery city | `Turlock` |
| `{{pickup_window_tz}}` | Pickup appointment window + tz | `Thu 6-8am PT` |
| `{{delivery_window_tz}}` | Delivery appointment window + tz | `Thu 1-3pm PT` |
| `{{change_summary}}` | Explicit change for variant C | `Pickup window changed to Thu 2-4pm PT at Diamond Walnut, Stockton` |

**Removed from SMS body (by design):** `driver_first` greeting, `dispatcher` signature, `commodity`.  
**Why:** length/spam headroom; greeting/signature burned budget without operational value. Commodity stays on the dispatch record / BOL — SMS is a nudge keyed by `load_id`, not the full order. If a multi-commodity day needs commodity in-SMS, append only when septet budget allows after required fields (optional future flag).

Appointment fields are **separate** for pickup vs delivery. Dates in windows must be unambiguous for delayed reads (prefer weekday + time + `PT`, or explicit calendar date when &gt;7 days out).

---

## 5) Required content rules (all variants)

1. Sender ID: `FCT Dispatch` once.  
2. Opt-out: fixed text `Reply STOP to opt out.` (not a free variable).  
3. Transactional only: no promos, referrals, or upsell.  
4. Encoding + length per §3.  
5. No PHI / SSN / full street address — city + facility name is enough.  
6. Variant B must **not** claim the truck is en route merely because it is loaded.  
7. Variant C must state the **changed field/value** (door, window, destination, etc.) — restating the full lane is not enough.

---

## 6) Approved templates (v1.1 — ChatGPT proposal + Grok red-team)

### A — Preload / assigned (`preload`)

```
FCT Dispatch: {{load_id}} assigned. Pickup {{pu_name}}, {{pu_city}} {{pickup_window_tz}}; to {{del_name}}, {{del_city}}. Reply STOP to opt out.
```

### B — Loaded / ready for delivery (`preloaded-delivery`)

```
FCT Dispatch: {{load_id}} loaded. Deliver to {{del_name}}, {{del_city}} {{delivery_window_tz}}. Reply STOP to opt out.
```

### C — Live update (`live-load`)

```
FCT Dispatch: {{load_id}} update: {{change_summary}}. Reply STOP to opt out.
```

---

## 7) Measured sample renders (GSM-7, 1 segment)

- **A (119):** `FCT Dispatch: L-2403 assigned. Pickup Diamond Walnut, Stockton Thu 6-8am PT; to Purina, Turlock. Reply STOP to opt out.`
- **B (92):** `FCT Dispatch: L-2403 loaded. Deliver to Purina, Turlock Thu 1-3pm PT. Reply STOP to opt out.`
- **C (118):** `FCT Dispatch: L-2403 update: Pickup window changed to Thu 2-4pm PT at Diamond Walnut, Stockton. Reply STOP to opt out.`

These are ordinary cases, not a worst-case ceiling. Stress before go-live: long facility names, accented names (force GSM fold or reject), explicit calendar dates, door changes, destination changes.

**Retired v1.0 originals:** used Unicode arrow/en-dash → UCS-2 → ~3 segments despite &lt;160 visible chars. Do not ship.

---

## 8) Pre-send gate (implementer checklist)

- [ ] All required fields present for variant  
- [ ] No unresolved placeholders  
- [ ] GSM-7 only after substitution  
- [ ] Septet count ≤ 160 (prefer ≤ 150)  
- [ ] STOP text exact  
- [ ] Recipient has documented consent; not on opt-out / STOP list (incl. queued retries)  
- [ ] Event state matches variant (assigned / loaded / update)  

Failed gate → show dispatch a correction UI; do not auto-send.

---

## 9) Reviewer checklist

### ChatGPT (content) — done 2026-09-05
- [x] Sender identification present  
- [x] STOP opt-out present  
- [x] No marketing content  
- [x] Unicode/multipart risk flagged on v1.0  
- [x] Proposed shorter GSM-safe variants  
- [x] Explicitly separate wording vs consent/system compliance  

### Grok (length / spam red-team) — done 2026-09-05
- [x] Confirmed v1.0 UCS-2 / 3-segment risk  
- [x] Accepted ChatGPT A/B/C with GSM-7-only + field caps  
- [x] Reconciled length contract to single-segment (job &lt;160 wins over soft 320)  
- [x] Confirmed commodity omission OK (dispatch record remains source of truth)  
- [x] No live send / no self-approval of Twilio ops  

---

## 10) Out of scope here

Twilio signup, A2P brand/campaign approval, opt-out webhook, quiet-hour policy, consent ledger — tracked on job `69ea39bf` and ops. Verify sender setup + opt-out behavior there **before** live use.
