#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: 3Modè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 TIMESTAMPConfiguration
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/webhookEndpoints API
| Méthode | Route | Requête | Réponse | Notes |
|---|---|---|---|---|
| POST | /api/v1/sms/send | {phone, template_key, variables | 201 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/webhook | 201 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