Requested by Senyo (Kennedy) · Produced by Roger · Source: sms-strategy-2026-03.md
High-level audit findings across the SMS strategy v2.0 document.
| # | Risk | Impact | Severity |
|---|---|---|---|
| 1 | APP+MAMBU duplication not enforced by doc structure — §5 says "disable Mambu DPD" but §6/§16 still list templates that could fire from either source | 20K+ customers/day double-messaged until engineering confirms kill switch | P0 |
| 2 | Frequency caps mathematically exceeded by scheduled plan — DPD 1-7 cap = 2/day, 4/week, 8/month. But DPD schedule + post-call + salary week + PTP can stack to 4/day | Stacking 960 → 0 goal impossible without hard priority/preemption logic | P0 |
| 3 | DPD messages defined in 4 separate sections (§6, §7, §15, §16) with inconsistent copy/timing | Implementation will pick one — which is canonical? Confusion → wrong templates deployed | P1 |
| 4 | PTP lifecycle overlaps DPD schedule with no preemption rule — borrower at DPD 3 with a PTP gets DPD_3 + PTP_REMIND on same day | PTP should suppress DPD during active promise window | P1 |
| 5 | Salary-week overlay creates uncapped burst — SAL_* fires on 22nd for ALL DPD 1+ borrowers, ON TOP of their regular DPD schedule | 22nd of month = worst stacking day. Caps must account for overlay. | P1 |
Every instance where the same message concept appears in multiple places, creating implementation ambiguity or actual customer duplication.
| ID | Type | Tags Involved | Where in Doc | What's Duplicated | Keep | Remove/Merge |
|---|---|---|---|---|---|---|
| DUP-01 | System Duplication | MAMBU DPD + APP DPD | §2.3 vs §5 | APP+MAMBU fire same DPD reminders. §2.3 shows MAMBU sends 868K/mo including DPD reminders. §5 says disable but no enforcement mechanism in doc. | APP (Communication Service) | Remove: All MAMBU DPD triggers. §5.2 Step 1 must be gated before ANY new templates go live. |
| DUP-02 | Template Duplication | DPD_0 through DPD_90 | §6 vs §15 vs §16 | DPD templates defined 3 times: §6 (interaction plan), §15 (message templates), §16 (full table). Copy is identical but if one section is edited and others aren't → drift. | §16 as canonical (full table) | Merge: §6 and §15 should reference §16. Remove inline copy from §6, make §15 a copy-paste appendix that's auto-generated from §16. |
| DUP-03 | Template Duplication | POSTCALL_ANS, POSTCALL_NA, POSTCALL_NA_WA | §7 vs §9 vs §15.5 vs §16.2 | Post-call templates in 4 places. §7 (event table), §9 (dedicated section), §15.5 (template list), §16.2 (full event table). | §16.2 as canonical | Merge: §9 keeps logic/rules. §7 and §15.5 reference §16.2 for copy. Remove inline templates from §7. |
| DUP-04 | Template Duplication | PTP_CONFIRM, PTP_REMIND, PTP_BROKEN, PTP_BROKEN_ESC | §7 vs §8 vs §15.6 vs §16.2 | PTP templates in 4 places. Same pattern as post-call. | §16.2 as canonical | Merge: §8 keeps lifecycle logic. Templates reference §16.2. |
| DUP-05 | Template Duplication | SAL_EARLY, SAL_MID, SAL_LEGAL, SAL_DEEP | §3.2 vs §15.7 vs §16.2 | Salary templates in 3 places. §3.2 defines them inline with rules. §15.7 repeats copy. §16.2 repeats again. | §16.2 as canonical | Merge: §3.2 keeps rules only, references §16.2 for copy. |
| DUP-06 | Concept Overlap | DPD_3 vs DPD_3_EDU | §6.3 vs §12.4 | Two DPD 3 messages. DPD_3 (standard) and DPD_3_EDU (with landing page link). Which fires? Both? A/B test? Unclear. |
DPD_3_EDU (richer) | Resolve: Either replace DPD_3 with DPD_3_EDU, or explicitly state DPD_3_EDU is an A/B variant. Don't ship both to same borrower. |
| DUP-07 | Concept Overlap | DPD_14 vs DPD_14_EDU | §6.4 vs §12.4 | Two DPD 14 SMS messages. Same issue as DUP-06. Plus DPD_14_EMAIL = 3 touchpoints at DPD 14. | DPD_14_EDU + DPD_14_EMAIL | Resolve: DPD_14_EDU replaces DPD_14 for SMS. Email is supplementary (OK). Remove vanilla DPD_14 SMS. |
| DUP-08 | Concept Overlap | DPD_28 vs DPD_28_EDU | §6.4 vs §12.4 | Two DPD 28 SMS messages. Same pattern. | DPD_28_EDU (includes link) | Resolve: Replace DPD_28 with DPD_28_EDU once short links are live. Until then, keep DPD_28. |
| DUP-09 | Concept Overlap | POSTCALL_NA vs POSTCALL_NA_WA vs POSTCALL_NA_EVE | §9.2 vs §16.2 | Three "not answered" variants. NA (DPD 1-13), NA_WA (DPD 14+), NA_EVE (evening). Can a DPD 14+ not-answered borrower get both NA_WA at 17:00 AND NA_EVE at 18:00? | POSTCALL_NA (DPD 1-13), POSTCALL_NA_WA (DPD 14+) | Remove: POSTCALL_NA_EVE — redundant with NA/NA_WA. The 17:00 send covers evening already. |
| DUP-10 | Channel Overlap | PREDUE_25 (push) + PREDUE_15 (push→SMS) | §6.2 | DPD -25 and -15 both go push→SMS. §6.2 says -25 = push only, -15 = push→SMS. But "changes from current" says -15 LN<3 duplicate was "merged." Into what? Clarification needed. | Both (different intent) | Clarify: Add explicit rule — what happens to LN<3 cohort? Is DPD -15 now universal regardless of loan number? |
| DUP-11 | Section Duplication | Architecture diagram | §5.3 vs Appendix B | Target architecture drawn twice — §5.3 (ASCII) and Appendix B (ASCII). Different level of detail but could diverge. | Appendix B (more complete) | Merge: §5.3 references Appendix B. Remove inline diagram from §5.3. |
| DUP-12 | Data Duplication | Send time specs | §6 (per DPD) vs §13 (by slot) | Send times specified twice — per-DPD in §6 tables and summarized in §13. If §6 changes a time, §13 must update too. | §6 as source of truth | Merge: §13 should be auto-derivable from §6. Add note: "This section summarizes §6 — do not edit independently." |
| DUP-13 | Suppression Logic | Suppression rules | §10.4 vs §14.3 vs §14.4 | Suppression/dedup logic in 3 places: §10.4 (reference number suppression), §14.3 (Senyo alignment), §14.4 (pre-send checks). Rules are consistent but spread across sections. | §14.4 as canonical engine spec | Merge: §10.4 and §14.3 reference §14.4. One dedup spec to implement. |
| DUP-14 | Compliance Rules | BoG language guardrails | §11.3 vs Appendix D | Compliance do/don't lists in 2 places. §11.3 (BoG-specific) and Appendix D (full checklist). Overlap on BoG assertion language rules. | Appendix D as master checklist | Merge: §11.3 is narrative context. Appendix D is the executable checklist. Cross-reference, don't duplicate rules. |
Contradictions, logical impossibilities, and specification gaps that would break implementation.
| ID | Conflict Type | Where | What It Says | Why It's a Problem | Recommended Fix |
|---|---|---|---|---|---|
| CON-01 | Frequency Cap vs Actual Touchpoints | §14.1 vs §6+§7+§3.2 | §14.1: Early Due max = 2 SMS/day, 4/week, 8/month. But a DPD 3 borrower on salary week with a PTP could get: DPD_3 (06:30) + SAL_EARLY (06:30) + PTP_REMIND (event) + POSTCALL_NA (17:00) = 4 SMS in one day. | §14.2 says post-call and PTP are "separate from DPD cap" — so they DON'T count toward 2/day. This means the 2/day cap is misleading. Actual max = 2 (DPD) + 1 (post-call) + 1 (PTP) = 4/day. | Define a GLOBAL hard ceiling of 3 SMS/day regardless of type. DPD cap = 2, post-call = 1, PTP = 1 — but if any 2 fire, suppress the lowest priority. Add priority order: PTP > post-call > salary > DPD. |
| CON-02 | Salary Week vs DPD Schedule | §3.2 vs §6 vs §14 | §3.2: SAL_* fires on 22nd "in addition to regular schedule." §6: regular DPD fires daily. §14.1: SAL counts toward cap. But §3.2 says it's sent "once at start of salary week." | On the 22nd, a DPD 5 borrower gets: DPD_5 (12:00) + SAL_EARLY (06:30) = 2 SMS. Fine. But if DPD 5 falls on 22nd AND they have a PTP AND weren't answered → 4 SMS. Cap says 2/day for DPD bucket. | SAL_* replaces the regular DPD SMS on the 22nd, doesn't add to it. Or: SAL_* counts as the DPD slot for that day (preempts, not supplements). |
| CON-03 | PTP vs DPD Timing Clash | §8 vs §6 | §8: PTP lifecycle fires on promise date ±1d. §6: DPD schedule fires regardless. No preemption rule exists. | Borrower at DPD 7 with PTP for tomorrow: gets DPD_7 (12:00) + PTP_REMIND (event). If PTP is broken next day: DPD_8 (no template but salary week could fire) + PTP_BROKEN. PTP should be the priority — it's a live commitment. | Add rule: Active PTP suppresses DPD SMS from PTP_CONFIRM until PTP_BROKEN + 24h. PTP is a higher-signal interaction. Let it run its course. |
| CON-04 | Post-Call Timing vs DPD Schedule | §9.3 vs §6 | §9.3: "Post-call SMS does NOT count against DPD-trigger frequency cap." §14.2: "Max SMS/borrower/day = 2" (global). These contradict. | Is the global cap of 2/day absolute? Or are post-call and PTP exempt? If exempt, the "global cap" isn't global. If not exempt, §9.3 is wrong. | Rewrite §14.2: "Max DPD-scheduled SMS = 2/day. Max total SMS (all types) = 3/day. Priority: PTP > post-call > DPD." Remove "separate from cap" language in §9.3. |
| CON-05 | DPD -1 and DPD 0 Same-Day Collision | §6.2 + §6.3 | PREDUE_1 fires at 06:30 on the day before due. DPD_0 fires at 06:30 on the due date. But a borrower whose loan was due "today" gets PREDUE_1 yesterday + DPD_0 today = fine. HOWEVER, what if payment processing delays make a borrower show as DPD -1 AND DPD 0 on the same snapshot? | Edge case: loan due date straddles midnight UTC. Mambu/APP might fire both on same calendar day depending on timezone handling (Ghana = UTC, so less risky, but not impossible with batch timing). | Add dedup rule: If PREDUE_1 sent in last 24h, suppress DPD_0. Same-message cooldown should catch this if MESSAGE_TAG dedup is tight enough. Verify batch timing in Communication Service. |
| CON-06 | §14.1 Caps vs §17.3 Volume Projections | §14.1 vs §17.3 | §14.1: Pre-Due max = 5 SMS/month. §6.2: 6 pre-due touchpoints (DPD -25, -15, -9, -5, -3, -1). That's 6 messages in ~25 days if the loan is active the whole period. | 6 scheduled touchpoints > 5/month cap. At least one will be suppressed. Which one? No priority order defined for pre-due. | Either increase pre-due cap to 6, or drop one touchpoint. Recommend dropping PREDUE_25 (push-only, awareness, lowest value). That gives exactly 5: -15, -9, -5, -3, -1. |
| CON-07 | Written-Off "1 SMS/month" Cap vs Template Count | §14.1 vs §6.8 | §14.1: Written-Off cap = 1 SMS/month. §6.8: WO has monthly cadence (M1, M2, M3) then quarterly. But also SAL_DEEP fires during salary week for DPD 91-150. | Written-off accounts (DPD 150+) shouldn't get SAL_DEEP (which is DPD 91-150). But the boundary is fuzzy — does "Legal_3 DPD 91-150" include written-off? §6.7 says "DPD 91-150, IVR-only outside salary week." | Clarify DPD boundary: Written-off = DPD 150+ OR account_state = WRITTEN_OFF (whichever comes first). SAL_DEEP stops at write-off. WO_* templates are the only channel for written-off accounts. |
| CON-08 | Quiet Hours vs Salary Week Rules | §13.4 vs §13.5 | §13.4: "No SMS between 20:00 and 06:30." §13.5: Saturday/Sunday = no SMS unless salary week. §3.2: Salary week "includes weekends." But 06:30 Saturday SMS during salary week — is that OK? | The rules don't explicitly say salary week overrides quiet hours on weekends. 06:30 could be seen as respecting the 06:30 boundary. But should Saturday/Sunday morning SMS exist at all outside urgency? | Add explicit rule: "Salary week enables Saturday 08:30–18:00 only. Sunday remains suppressed. Quiet hours (20:00–06:30) are NEVER overridden." |
| CON-09 | Education Links Before Infrastructure | §12.1 vs §12.4 vs §15.12 | §12.1 lists 5 landing pages as "TO BUILD." §12.4 and §15.12 define templates with fido.link/* URLs. §19 says "Phase 4: Short Link Tracking (Day 4-5)." |
Templates reference infrastructure that doesn't exist yet. If DPD_3_EDU fires before fido.link/penalties is built, borrowers click a dead link. Worse than no link. |
Gate EDU templates behind infrastructure readiness. Phase 1-3 use standard templates (no links). Phase 4+ switches to EDU variants. Add explicit dependency in §19. |
| CON-10 | Suppression Stream vs Frequency Caps | §14.3 vs §15.8 | §14.3: Temp suppression = "1 SMS/week." §15.8: SUPP_ENTRY, SUPP_W1, SUPP_W2, SUPP_W3 = 4 templates over ~3 weeks. That's ~1.3/week. OK. But SUPP_ENTRY fires "on event" and SUPP_W1 could be in the same week if suppression entered on day 5. | If borrower enters suppression on a Thursday, SUPP_ENTRY fires Thursday. SUPP_W1 is "Week 1" — does that mean next Thursday? Or the following Monday? Ambiguous timing. | Define suppression stream timing explicitly: SUPP_ENTRY = day 0, SUPP_W1 = day 7, SUPP_W2 = day 14, SUPP_W3 = day 21. Not "week 1/2/3" — absolute days from entry. |
| CON-11 | Compliance Wording Inconsistency | §11.2 vs §15.4 | §11.2: DPD 35 language = "may be classified as wilful defaulter." §15.4 DPD_35 template: "under BoG rules you may be classified as wilful defaulter." These match. BUT §15.4 DPD_42: "Wilful default = public naming + credit ban" — this is an assertion ("="), not a "may." | DPD_42 template violates §11.3 compliance guardrails which say "never assert." The "=" framing implies certainty about classification. | Rewrite DPD_42: "{name}, final warning. Continued non-payment may result in public naming under BoG rules. Resolve now: {wa}" — keeps "may" framing. |
Ordered by priority. Each step has an owner, validation criteria, and dependency chain.
LoanReminderService in production. Set configuration.setEnabled(false) or empty the reminders list. MAMBU queue continues for transactional SMS (OTP, receipt, disbursement) only.SELECT COUNT(*) FROM COMMUNICATION_RESULT WHERE SOURCE='MAMBU' AND MESSAGE_TAG LIKE 'DPD%' AND CREATED_AT > NOW() - INTERVAL '24h' → must return 0 for 3 consecutive days.PTP_ACTIVE = true → skip DPD triggers.
grep -c "≤160 chars" sms-strategy.md → templates appear in only 2 places: §16.1 and §16.2.grep "POSTCALL_NA_EVE" sms-strategy.md → 0 results.{name}, continued non-payment may result in public naming under BoG directive. Resolve now: {wa}For every major component of the doc — keep as-is, merge, remove, or rewrite.
| Tag | Action | Reason |
|---|---|---|
| PREDUE_25 | KEEP | Push-only. Low cost. Good awareness. |
| PREDUE_15 | KEEP | Clarify LN<3 audience rule. |
| PREDUE_9 | KEEP | Differentiated timing. Within cap if PREDUE_25 is push-only. |
| PREDUE_5 | KEEP | Core pre-due touchpoint. |
| PREDUE_3 | KEEP | Push-only. Educational. |
| PREDUE_1 | KEEP | Final reminder. High value. Add DPD_0 dedup guard. |
| DPD_0 through DPD_90 | KEEP | Core schedule. Fix DPD_42 compliance wording. |
| DPD_3_EDU, DPD_14_EDU, DPD_28_EDU | MERGE → Phase 4 | Replace standard variants once short links are live. |
| DPD_14_EMAIL | KEEP | Supplementary channel. No conflict. |
| POSTCALL_ANS | KEEP | High-value event-driven touchpoint. |
| POSTCALL_NA | KEEP | DPD 1-13 not-answered. Core. |
| POSTCALL_NA_WA | KEEP | DPD 14+ not-answered. Adds WhatsApp CTA. |
| POSTCALL_NA_EVE | REMOVE | Redundant with NA/NA_WA at 17:00. Stacking risk. |
| PTP_CONFIRM/REMIND/BROKEN | KEEP | Core PTP lifecycle. |
| PTP_BROKEN_ESC | KEEP | DPD 31+ escalation after broken PTP. |
| SAL_EARLY/MID/LEGAL/DEEP | REWRITE | Keep templates. Rewrite rules: replaces DPD, doesn't add. |
| PAY_PARTIAL/FULL/PROGRESS_50/75 | KEEP | Positive reinforcement. No conflicts. |
| SUPP_ENTRY/W1/W2/W3 | REWRITE | Keep copy. Rewrite timing to absolute day offsets. |
| REF_POLITE/FOLLOWUP | KEEP | Compliance-safe. Well-defined. |
| WO_M1/M2/M3/QUARTERLY | KEEP | New revenue channel. Properly capped. |
| DISC_OFFER/7D/14D | KEEP | Discount lifecycle. Clean. |
| LEGAL3_MONTHLY | KEEP | IVR primer. Low volume. |
| APP_INSTALL | KEEP | Push-only re-engagement. |
| Order | Action | Blocks | Owner |
|---|---|---|---|
| 1 | Kill MAMBU DPD (Step 1) | Everything | Engineering |
| 2 | Doc cleanup: consolidate templates, fix caps (Steps 5, 7, 8, 10) | Implementation | Doc Owner (Roger) |
| 3 | Build priority/preemption engine (Step 2) | New campaigns | Engineering |
| 4 | Add PTP suppression window (Step 3) | PTP launch | Engineering |
| 5 | Salary week replacement rule (Step 4) | 22nd testing | Senyo confirms |
| 6 | Fix DPD_42 compliance (Step 9) | Template deployment | Compliance review |
| 7 | Remaining P2/P3 fixes (Steps 11-18) | Nothing critical | Doc Owner |