Skip to content

#61 — Moteur de Templates SMS

PLANIFIÉ

Priorité: 🔴 CRITIQUE · Type: TYPE B · Conteneur: rgz-api · Code: app/services/sms_templates.pyDépendances: #1 rgz-api, #12 otp-sms


Description

Le moteur de templates SMS centralise tous les modèles SMS de la plateforme : OTP (6 chiffres), alertes incidents P0/P1/P2, crédits SLA, factures, messages onboarding. Basé sur Jinja2, les templates permettent l'injection de variables dynamiques sans modification de code. Chaque template est versionné et archivé pour audit.

Intégration Letexto API pour envoi réel. Rate limiting intégré (max 3 OTP/min par phone, max 5 incidents/hour par revendeur) via Redis. Support callback webhooks pour tracking delivery status (sent, delivered, failed, bounced).


Templates Principaux

TEMPLATE 1: OTP_VERIFICATION
  Clé: otp_verification
  Destinataire: subscriber (phone)
  Variables: code (6 chars), minutes (TTL)
  Contenu:
    "Votre code RGZ: {{ code }}. Valide {{ minutes }}min. Ne le partagez pas."
  Exemple: "Votre code RGZ: 123456. Valide 5min. Ne le partagez pas."
  Longueur: 72 chars (1 SMS)
  SLA: < 10s delivery
  Retry: 3 tentatives si Letexto error

TEMPLATE 2: INCIDENT_P0_ALERT
  Clé: incident_p0_alert
  Destinataire: reseller (phone)
  Variables: incident_number, title, affected_sites, delay (min)
  Contenu:
    "🔴 ALERTE RGZ P0: {{ title }}. {{ affected_sites }} sites hors service. Équipe mobilisée. MAJ dans {{ delay }}min. Ticket: {{ incident_number }}"
  Exemple: "🔴 ALERTE RGZ P0: Fiber cut central. 45 sites hors service. Équipe mobilisée. MAJ dans 5min. Ticket: INC-2026-000847"
  Longueur: ~160 chars (1 SMS)
  SLA: < 30s delivery (urgent)
  Retry: 5 tentatives

TEMPLATE 3: INCIDENT_P1_ALERT
  Clé: incident_p1_alert
  Destinataire: reseller
  Variables: incident_number, title, affected_sites
  Contenu:
    "⚠️ Alerte RGZ P1: {{ title }}. {{ affected_sites }} sites dégradés. Support saisi. Ticket: {{ incident_number }}. Status: https://status.rgz.bj"
  Longueur: ~130 chars
  SLA: < 1min delivery
  Retry: 3

TEMPLATE 4: INCIDENT_P2_ALERT
  Clé: incident_p2_alert
  Destinataire: reseller
  Variables: incident_number, title
  Contenu:
    "ℹ️ Incident RGZ P2: {{ title }}. Support en cours. Ticket: {{ incident_number }}"
  Longueur: ~90 chars
  SLA: < 4h delivery
  Retry: 2

TEMPLATE 5: SLA_CREDIT_APPLIED
  Clé: sla_credit_applied
  Destinataire: subscriber
  Variables: amount_fcfa, downtime_minutes, sla_percent
  Contenu:
    "RGZ crédit appliqué: {{ amount_fcfa }} FCFA (downtime {{ downtime_minutes }}min, uptime {{ sla_percent }}%). Merci!"
  Exemple: "RGZ crédit appliqué: 1000 FCFA (downtime 15min, uptime 99.7%). Merci!"
  Longueur: ~130 chars
  SLA: Same day
  Retry: 2

TEMPLATE 6: BILLING_INVOICE_READY
  Clé: billing_invoice_ready
  Destinataire: reseller
  Variables: month, amount_fcfa, link_portal
  Contenu:
    "Facture {{ month }} prête: {{ amount_fcfa }} FCFA. Télécharger: {{ link_portal }}/invoices/latest. Merci!"
  Exemple: "Facture février 2026 prête: 450000 FCFA. Télécharger: https://admin-rgz.duckdns.org/invoices/latest. Merci!"
  Longueur: ~140 chars
  SLA: < 1min (auto-sent daily 00:15)
  Retry: 3

TEMPLATE 7: ONBOARDING_WELCOME
  Clé: onboarding_welcome
  Destinataire: reseller contact_phone
  Variables: reseller_name, created_at
  Contenu:
    "Bienvenue {{ reseller_name }} sur RGZ! Candidature reçue le {{ created_at }}. Validation en cours (2-3j). Statut: {{ link_portal }}"
  Longueur: ~130 chars
  SLA: Immediate (lors création compte)
  Retry: 3

TEMPLATE 8: ONBOARDING_APPROVED
  Clé: onboarding_approved
  Destinataire: reseller
  Variables: reseller_name, vlan_number, nas_id
  Contenu:
    "{{ reseller_name }}: Documents validés! ✓ VLAN {{ vlan_number }} alloué. NAS-ID: {{ nas_id }}. Dashboard: {{ link_portal }}"
  Longueur: ~140 chars
  SLA: < 1min (post-validation)
  Retry: 3

TEMPLATE 9: CUTOVER_REMINDER
  Clé: cutover_reminder
  Destinataire: revendeur
  Variables: reseller_name, items_pending_count, link_portal
  Contenu:
    "{{ reseller_name }}: Checklist cutover {{ items_pending_count }}/40 items en attente. Valider avant go-live: {{ link_portal }}/cutover"
  Longueur: ~140 chars
  SLA: 7 jours (si pas complété)
  Retry: 2

TEMPLATE 10: RMA_TICKET_CREATED
  Clé: rma_ticket_created
  Destinataire: revendeur
  Variables: rma_number, serial_number
  Contenu:
    "Ticket RMA {{ rma_number }} créé pour {{ serial_number }}. Statut: En diagnostic. Suivi: {{ link_portal }}/rma/{{ rma_number }}"
  Longueur: ~130 chars
  SLA: Immediate
  Retry: 3

Modèles de Données

python
class SmsTemplate(BaseModel):
    id: UUID
    key: str UNIQUE (otp_verification, incident_p0_alert, etc)
    name: str
    description: str
    content: str  # Jinja2 template
    required_variables: list[str]  # [code, minutes]
    category: str (otp|incident|billing|onboarding|rma)
    version: int
    is_active: bool
    created_at: datetime
    updated_by: UUID
    updated_at: datetime

# Table DB
sms_templates:
  - id UUID PK
  - key VARCHAR(100) UNIQUE
  - name VARCHAR(255)
  - description TEXT
  - content TEXT (Jinja2 template)
  - required_variables TEXT[] (JSON)
  - category VARCHAR(50) (otp|incident|billing|onboarding|rma)
  - version INT DEFAULT 1
  - is_active BOOLEAN DEFAULT true
  - created_by UUID FK users
  - created_at TIMESTAMP
  - updated_by UUID FK users
  - updated_at TIMESTAMP

sms_audit_log:
  - id UUID PK
  - template_id UUID FK
  - recipient_phone TEXT (E164)
  - content_sent TEXT (final rendered)
  - status VARCHAR(50) CHECK(sent|delivered|failed|bounced)
  - letexto_message_id VARCHAR(100) NULL
  - error_message TEXT NULL
  - sent_at TIMESTAMP
  - delivered_at TIMESTAMP NULL
  - source_endpoint VARCHAR(100) (incident|otp|billing|etc)
  - created_at TIMESTAMP

Configuration

env
# .env.example
LETEXTO_API_KEY=your_api_key
LETEXTO_SENDER_ID=RGZ_BENIN
LETEXTO_ENDPOINT=https://api.letexto.com/v1/send
LETEXTO_WEBHOOK_SECRET=webhook_secret_key
LETEXTO_TIMEOUT_SECONDS=10
LETEXTO_RETRY_MAX_ATTEMPTS=3
LETEXTO_RETRY_BACKOFF_SECONDS=5

SMS_RATE_LIMIT_OTP=3/min
SMS_RATE_LIMIT_INCIDENT=5/hour
SMS_RATE_LIMIT_BILLING=1/day

SMS_AUDIT_RETENTION_DAYS=365
SMS_CALLBACK_URL=https://api-rgz.duckdns.org/api/v1/sms/webhook

Endpoints API

MéthodeRouteRequêteRéponseNotes
POST/api/v1/sms/send{phone, template_key, variables201 CREATED
GET/api/v1/sms/templates{items:[{id, key, name, required_variables}], total}200 OK
GET/api/v1/sms/templates/200 OK
POST/api/v1/sms/templates/{key}/test{variables:200 OK
PUT/api/v1/sms/templates/200 OK (admin)
POST/api/v1/sms/webhook201 CREATED (Letexto callback)
GET/api/v1/sms/audit?recipient={phone}&limit=50{items:[{phone, content_sent, status, sent_at}], total}200 OK

Commandes Utiles

bash
# Envoyer OTP (API caller, eg #12 otp-sms)
curl -X POST http://localhost:8000/api/v1/sms/send \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+229-97979964",
    "template_key": "otp_verification",
    "variables": {
      "code": "123456",
      "minutes": 5
    },
    "idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
  }'

# Envoyer alerte incident P0
curl -X POST http://localhost:8000/api/v1/sms/send \
  -d '{
    "phone": "+229-97979965",
    "template_key": "incident_p0_alert",
    "variables": {
      "incident_number": "INC-2026-000847",
      "title": "Fiber cut central",
      "affected_sites": 45,
      "delay": 5
    }
  }'

# Lister templates disponibles
curl http://localhost:8000/api/v1/sms/templates

# Tester template rendering (admin)
curl -X POST http://localhost:8000/api/v1/sms/templates/incident_p0_alert/test \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -d '{
    "variables": {
      "incident_number": "TEST-001",
      "title": "Test incident",
      "affected_sites": 10,
      "delay": 5
    }
  }'

# Modifier template (admin)
curl -X PUT http://localhost:8000/api/v1/sms/templates/incident_p0_alert \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -d '{
    "content": "🔴 ALERTE RGZ P0: {{ title }}. {{ affected_sites }} sites offline. Team on it. Update in {{ delay }}min.",
    "version_note": "English version for testing"
  }'

# Vérifier audit SMS pour revendeur (NOC)
curl http://localhost:8000/api/v1/sms/audit?recipient=+229-97979964 \
  -H "Authorization: Bearer NOC_TOKEN" | jq '.items[] | {sent_at, status, content_sent}'

# Webhook callback Letexto (reçu endpoint)
curl -X POST http://localhost:8000/api/v1/sms/webhook \
  -H "X-Letexto-Signature: hmac_sha256_signature" \
  -d '{
    "message_id": "letexto-msg-12345",
    "status": "delivered",
    "timestamp": "2026-02-21T14:30:00Z",
    "recipient": "+229-97979964"
  }'

Intégration Avec Autres Outils

  • #12 otp-sms: Appelle #61 pour envoyer OTP via template otp_verification
  • #58 incident-escalation: Appelle #61 pour alertes P0/P1/P2
  • #56 onboarding-reseller: Appelle #61 templates onboarding_* chaque étape
  • #57 rma-ticket-system: Appelle #61 pour notifications RMA
  • #19 moteur-facturation: Appelle #61 pour billing_invoice_ready
  • #25 credit-sla-auto: Appelle #61 pour sla_credit_applied

Implémentation TODO

  • [ ] CRUD templates (10 par défaut, extensible)
  • [ ] Jinja2 rendering engine (inject variables)
  • [ ] Letexto API client (send + retry)
  • [ ] Webhook callback handler (Letexto status updates)
  • [ ] Rate limiting Redis (OTP 3/min, incident 5/hour)
  • [ ] Idempotency key check (Redis set to prevent duplicate SMS)
  • [ ] Audit trail (sms_audit_log)
  • [ ] Admin template editor + test renderer
  • [ ] SMS length calculator (char count + SMS count)
  • [ ] Tests: render OTP → send → callback delivered
  • [ ] Dashboard audit (NOC view SMS by recipient)

Dernière mise à jour: 2026-02-21

PROJET MOSAÏQUE — 81 outils, 22 conteneurs, 500+ revendeurs WiFi Zone