Skip to content

#63 — Email Notification

PLANIFIÉ

Priorité: 🟠 HAUTE · Type: TYPE B · Conteneur: rgz-api · Code: app/services/email.pyDépendances: #1 rgz-api, #24 factures-pdf


Description

Service d'email SMTP pour notifications formelles : rapports mensuels revendeurs, factures PDF en pièce jointe, alertes incidents critiques, confirmations onboarding. Utilise Python smtplib + Jinja2 templates HTML. Support attachements (PDF, PNG, JSON).

Chaque email est archivé dans la base pour audit et retransmission. Rate limiting par destinataire (max 1/day marketing, pas de limite transactionnel). Les templates sont versionnées pour permettre évolutions sans déploiement code.


Templates d'Email Principaux

TEMPLATE 1: billing_invoice_monthly
  Sujet: "Facture RGZ — {{ month }} {{ year }}"
  Destinataire: reseller.contact_email
  Variables: month, year, amount_fcfa, link_portal, invoice_number
  Pièce jointe: invoice.pdf (généré par #24)
  Contenu:
    "Bonjour {{ reseller_name }},

     Votre facture du mois de {{ month }} {{ year }} est prête.

     Montant: {{ amount_fcfa }} FCFA
     Numéro de facture: {{ invoice_number }}
     Date: {{ invoice_date }}

     Télécharger facture: [LIEN PORTAL]
     ou en pièce jointe ci-dessous.

     Merci pour votre confiance!

     Cordialement,
     RGZ NOC Team"
  SLA: < 1min (auto-sent daily 00:15 by Celery task #70)
  Retry: 3 tentatives SMTP
  Audit: Enregistrer email_sent event + timestamp

TEMPLATE 2: incident_alert_critical
  Sujet: "⚠️ ALERTE CRITIQUE RGZ — {{ incident_number }}"
  Destinataire: reseller contact emails + noc@rgz.bj
  Variables: incident_number, title, description, affected_sites, detected_at, status_link
  Contenu:
    "ALERTE CRITIQUE

     Incident: {{ incident_number }}
     Titre: {{ title }}
     Sévérité: {{ priority }}
     Sites affectés: {{ affected_sites }}
     Détecté: {{ detected_at }}

     Descriptif:
     {{ description }}

     Status en temps réel: {{ status_link }}

     Escalade automatique activée.
     Merci de votre patience."
  SLA: < 30s (urgent, envoi immédiat #58)
  Retry: 5 tentatives
  Audit: Incident alert email log

TEMPLATE 3: onboarding_welcome
  Sujet: "Bienvenue sur RGZ Access!"
  Destinataire: reseller contact_email
  Variables: reseller_name, validation_link, estimated_duration
  Contenu:
    "Bienvenue {{ reseller_name }}!

     Votre candidature RGZ a été reçue avec succès.

     Processus onboarding (7-12 jours):
     1. Validation documents (1-2j)
     2. Création compte + VLAN (1j)
     3. Livraison CPE (3-5j)
     4. Installation terrain (2-3j)
     5. Tests iperf3 (1-2j)
     6. Go-live (1j)

     Suivi: {{ validation_link }}/dashboard

     Support: support@rgz.bj | +229-XXXXX"
  SLA: Immédiat (#56 étape 0)
  Retry: 2
  Audit: Onboarding welcome email

TEMPLATE 4: onboarding_approved
  Sujet: "✓ Documents approuvés — RGZ Access"
  Destinataire: reseller
  Variables: reseller_name, vlan_number, nas_id, next_steps
  Contenu:
    "Excellent {{ reseller_name }}!

     Vos documents ont été validés. ✓

     Informations techniques assignées:
     - VLAN: {{ vlan_number }}
     - NAS-ID: {{ nas_id }}
     - Subnet: 10.{{ vlan_number }}.0.0/24

     Prochaines étapes:
     {{ next_steps }}

     Dashboard: [LIEN]

     Merci!"
  SLA: < 1min (#56 étape 1→2)
  Retry: 2
  Audit: Onboarding approval email

TEMPLATE 5: cutover_reminder
  Sujet: "Rappel — Checklist cutover {{ reseller_name }}"
  Destinataire: reseller
  Variables: reseller_name, items_done, items_total, link_portal
  Contenu:
    "Bonjour {{ reseller_name }},

     Votre checklist cutover est à {{ items_done }}/{{ items_total }} éléments.

     Pour passer en production, tous les 40 points doivent être validés.

     Avancée: [BARRE PROGRESS]

     Valider maintenant: {{ link_portal }}/cutover

     Support: support@rgz.bj"
  SLA: Si délai > 7j sans progression
  Retry: 1
  Audit: Cutover reminder email

TEMPLATE 6: rma_confirmation
  Sujet: "Ticket RMA {{ rma_number }} — {{ serial_number }}"
  Destinataire: reseller
  Variables: rma_number, serial_number, status, link_tracking
  Contenu:
    "Ticket RMA créé avec succès.

     Numéro: {{ rma_number }}
     Produit: {{ serial_number }}
     Statut: {{ status }}

     Suivi: {{ link_tracking }}

     Merci!"
  SLA: Immédiat (#57 création)
  Retry: 2
  Audit: RMA confirmation email

TEMPLATE 7: sla_credit_notification
  Sujet: "Crédit SLA appliqué — {{ amount_fcfa }} FCFA"
  Destinataire: subscriber.email (si fourni)
  Variables: amount_fcfa, downtime_minutes, sla_percent, invoice_number
  Contenu:
    "Crédit SLA appliqué ✓

     Montant: {{ amount_fcfa }} FCFA
     Raison: Downtime {{ downtime_minutes }}min
     Disponibilité: {{ sla_percent }}%

     Facturation: Appliqué à votre solde

     Merci pour votre patience!"
  SLA: < 5min (#25 credit-sla-auto)
  Retry: 2
  Audit: SLA credit email

TEMPLATE 8: support_ticket_created
  Sujet: "Ticket support #{{ ticket_number }} créé"
  Destinataire: user email
  Variables: ticket_number, title, priority, support_link
  Contenu:
    "Votre ticket support a été créé.

     Numéro: #{{ ticket_number }}
     Titre: {{ title }}
     Sévérité: {{ priority }}

     Suivi: {{ support_link }}

     Nous vous répondrons rapidement.

     Merci!"
  SLA: Immédiat
  Retry: 2
  Audit: Support ticket email

Modèles de Données

python
class EmailTemplate(BaseModel):
    id: UUID
    key: str UNIQUE
    subject: str (Jinja2)
    html_content: str (Jinja2)
    text_content: str (plain text fallback)
    required_variables: list[str]
    attachments_allowed: bool
    category: str (billing|incident|onboarding|rma|billing|support)
    version: int
    is_active: bool

class EmailMessage(BaseModel):
    id: UUID
    template_key: str
    recipient_email: str
    subject_rendered: str
    html_rendered: str
    attachments: list[{filename, content_type, data_base64}]
    status: str (queued|sent|delivered|bounced|complaint)
    sent_at: datetime | None
    delivered_at: datetime | None
    error_message: str | None

# Table DB
email_templates:
  - id UUID PK
  - key VARCHAR(100) UNIQUE
  - subject TEXT
  - html_content TEXT
  - text_content TEXT
  - required_variables TEXT[]
  - attachments_allowed BOOLEAN DEFAULT false
  - category VARCHAR(50)
  - version INT DEFAULT 1
  - is_active BOOLEAN DEFAULT true
  - created_by UUID FK
  - created_at TIMESTAMP
  - updated_by UUID FK
  - updated_at TIMESTAMP

email_messages:
  - id UUID PK
  - template_key VARCHAR(100)
  - recipient_email VARCHAR(255)
  - subject TEXT
  - html_content TEXT
  - attachments_count INT DEFAULT 0
  - status VARCHAR(20) CHECK(queued|sent|delivered|bounced|complaint)
  - sent_at TIMESTAMP NULL
  - delivered_at TIMESTAMP NULL
  - bounce_type VARCHAR(20) NULL (permanent|temporary)
  - bounce_subtype VARCHAR(50) NULL
  - error_message TEXT NULL
  - source_endpoint VARCHAR(100) (billing|incident|onboarding|rma|support)
  - created_at TIMESTAMP

email_attachments:
  - id UUID PK
  - message_id UUID FK
  - filename VARCHAR(255)
  - content_type VARCHAR(100)
  - size_bytes INT
  - data BYTEA (ou S3 reference)
  - created_at TIMESTAMP

Configuration

env
# .env.example
SMTP_HOST=mail.rgz.bj
SMTP_PORT=587
SMTP_USER=noreply@rgz.bj
SMTP_PASSWORD=your_smtp_password
SMTP_FROM_NAME=RGZ Notifications
SMTP_FROM_EMAIL=noreply@rgz.bj
SMTP_REPLY_TO=support@rgz.bj
SMTP_USE_TLS=true
SMTP_TIMEOUT_SECONDS=30
SMTP_RETRY_MAX_ATTEMPTS=3

EMAIL_RATE_LIMIT_TRANSACTIONAL=unlimited
EMAIL_RATE_LIMIT_MARKETING=1/day

EMAIL_AUDIT_RETENTION_DAYS=365
EMAIL_AUTO_ANONYMIZE_AFTER_DAYS=90

# Attachment settings
EMAIL_MAX_ATTACHMENT_SIZE_MB=10
EMAIL_ALLOWED_ATTACHMENT_TYPES=pdf,png,jpg,json,csv

# Templates directory
EMAIL_TEMPLATES_DIR=/app/templates/emails

Endpoints API

MéthodeRouteRequêteRéponseNotes
POST/api/v1/email/send{to, template_key, variables201 CREATED
GET/api/v1/email/templates{items:[{key, subject, category, version}], total}200 OK
GET/api/v1/email/templates/200 OK
POST/api/v1/email/templates/{key}/test{to, variables200 OK
PUT/api/v1/email/templates/200 OK (admin)
POST/api/v1/email/webhook200 OK (SES callback)
GET/api/v1/email/messages?to={email}&limit=50{items:[{id, to, subject, status, sent_at}], total}200 OK

Commandes Utiles

bash
# Envoyer facture email (Celery task)
curl -X POST http://localhost:8000/api/v1/email/send \
  -H "Content-Type: application/json" \
  -d '{
    "to": "reseller@techconnect.bj",
    "template_key": "billing_invoice_monthly",
    "variables": {
      "reseller_name": "Tech Connect SA",
      "month": "février",
      "year": "2026",
      "amount_fcfa": "450000",
      "link_portal": "https://admin-rgz.duckdns.org/invoices/latest",
      "invoice_number": "INV-2026-0001"
    },
    "attachments": [
      {
        "filename": "invoice_2026-02.pdf",
        "content_type": "application/pdf",
        "data_base64": "JVBERi0xLjQKJeLjz9MNCi..."
      }
    ]
  }'

# Envoyer alerte incident critique
curl -X POST http://localhost:8000/api/v1/email/send \
  -d '{
    "to": "noc@rgz.bj,reseller@techconnect.bj",
    "template_key": "incident_alert_critical",
    "variables": {
      "incident_number": "INC-2026-000847",
      "title": "Fiber cut central",
      "description": "Central BGP flap, 45 sites offline",
      "priority": "P0",
      "affected_sites": 45,
      "detected_at": "2026-02-21T14:30:00Z",
      "status_link": "https://status.rgz.bj"
    }
  }'

# Envoyer email onboarding welcome
curl -X POST http://localhost:8000/api/v1/email/send \
  -d '{
    "to": "john@techconnect.bj",
    "template_key": "onboarding_welcome",
    "variables": {
      "reseller_name": "Tech Connect SA",
      "validation_link": "https://admin-rgz.duckdns.org",
      "estimated_duration": "7-12 jours"
    }
  }'

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

# Tester template rendering
curl -X POST http://localhost:8000/api/v1/email/templates/billing_invoice_monthly/test \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -d '{
    "to": "test@rgz.bj",
    "variables": {
      "reseller_name": "Test Company",
      "month": "février",
      "year": "2026",
      "amount_fcfa": "450000",
      "link_portal": "https://admin-rgz.duckdns.org",
      "invoice_number": "TEST-001"
    }
  }'

# Consulter historique emails envoyés
curl http://localhost:8000/api/v1/email/messages?to=reseller@techconnect.bj \
  -H "Authorization: Bearer ADMIN_TOKEN" | jq '.items'

Intégration Avec Autres Outils

  • #24 factures-pdf: Génère PDF pièce jointe pour billing_invoice_monthly
  • #70 rapport-sla-mensuel: Appelle #63 pour envoyer rapports mensuels
  • #56 onboarding-reseller: Appelle #63 à chaque étape (welcome, approval, go-live)
  • #57 rma-ticket-system: Appelle #63 pour confirmations RMA
  • #58 incident-escalation: Appelle #63 pour alertes P0 critiques
  • #25 credit-sla-auto: Appelle #63 pour notifications crédit
  • #63: Lui-même appelé par plusieurs services

Implémentation TODO

  • [ ] CRUD templates (8+ par défaut)
  • [ ] Jinja2 rendering (subject + HTML)
  • [ ] SMTP client (Python smtplib)
  • [ ] Retry logic (3 tentatives avec backoff)
  • [ ] Attachment support (PDF, PNG, JSON, CSV)
  • [ ] Rate limiting (transactionnel = unlimited, marketing = 1/day)
  • [ ] Audit trail (email_messages + email_attachments)
  • [ ] Bounce/complaint webhook handler (Amazon SES)
  • [ ] Admin template editor + test renderer
  • [ ] Tests: send email → delivery callback → audit trail
  • [ ] Dashboard admin: email audit logs by recipient

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

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