espace-paie-odentas/SUPPORT_INTERNAL_NOTIFICATIONS.md

9.1 KiB

Notifications Internes du Support

Vue d'ensemble

Système de notification par email vers paie@odentas.fr pour alerter l'équipe support quand :

  1. Un utilisateur crée un nouveau ticket
  2. Un utilisateur répond à un ticket existant

📧 Types de notifications

1. Nouveau ticket créé (support-ticket-created)

Déclencheur : Quand un utilisateur (non-staff) crée un nouveau ticket via l'API POST /api/tickets

Template Email :

  • Sujet : [SUPPORT] Nouveau ticket : {sujet du ticket}
  • Titre : 🎫 Nouveau ticket support
  • Bouton CTA : "Voir le ticket" → /staff/tickets/{id}
  • Couleur bouton : Bleu (#3B82F6)

Informations affichées :

InfoCard :

  • Organisation
  • Code employeur
  • Créé par (nom de l'utilisateur)
  • Email (email de l'utilisateur)

DetailsCard :

  • ID du ticket
  • Sujet
  • Catégorie (actuellement "Support général")
  • Message initial (avec sauts de ligne préservés)

2. Réponse utilisateur (support-ticket-reply)

Déclencheur : Quand un utilisateur (non-staff) ajoute un message à un ticket via POST /api/tickets/[id]/messages

Template Email :

  • Sujet : [SUPPORT] Réponse au ticket : {sujet du ticket}
  • Titre : 💬 Réponse sur un ticket
  • Bouton CTA : "Voir le ticket" → /staff/tickets/{id}
  • Couleur bouton : Bleu (#3B82F6)

Informations affichées :

InfoCard :

  • Organisation
  • Code employeur
  • Répondu par (nom de l'utilisateur)
  • Email (email de l'utilisateur)

DetailsCard :

  • ID du ticket
  • Sujet
  • Statut (open, in_progress, resolved, closed)
  • Réponse (avec sauts de ligne préservés)

🔧 Implémentation technique

Fichiers modifiés

1. /lib/emailTemplateService.ts

// Nouveaux types ajoutés
| 'support-ticket-created'
| 'support-ticket-reply'

// Nouveaux champs dans EmailDataV2
ticketCategory?: string;
ticketMessage?: string;
ticketStatus?: string;
userEmail?: string;
userMessage?: string;

2. /lib/emailMigrationHelpers.ts

Nouvelle fonction : sendInternalTicketCreatedEmail()

export async function sendInternalTicketCreatedEmail(
  data: {
    ticketId: string;
    ticketSubject: string;
    ticketCategory: string;
    ticketMessage: string;
    userName: string;
    userEmail: string;
    organizationName?: string;
    employerCode?: string;
  }
)
  • Convertit automatiquement les sauts de ligne en <br>
  • Envoie toujours à paie@odentas.fr
  • CTA vers /staff/tickets/{id}

Nouvelle fonction : sendInternalTicketReplyEmail()

export async function sendInternalTicketReplyEmail(
  data: {
    ticketId: string;
    ticketSubject: string;
    ticketStatus: string;
    userMessage: string;
    userName: string;
    userEmail: string;
    organizationName?: string;
    employerCode?: string;
  }
)
  • Convertit automatiquement les sauts de ligne en <br>
  • Envoie toujours à paie@odentas.fr
  • CTA vers /staff/tickets/{id}

3. /app/api/tickets/route.ts

Modifications dans POST :

// Après la création du ticket, si !isStaff
if (!isStaff) {
  try {
    // Récupérer les infos utilisateur avec createSbServiceRole()
    const sbAdmin = createSbServiceRole();
    const { data: userData } = await sbAdmin.auth.admin.getUserById(user.id);
    
    // Récupérer org et code employeur
    const { data: organization } = await sb.from('organizations')...
    const { data: orgDetails } = await sb.from('organization_details')...
    
    // Envoyer la notification
    await sendInternalTicketCreatedEmail({...});
  } catch (emailError) {
    // Ne pas bloquer la création du ticket
    console.error('Failed to send internal notification');
  }
}

4. /app/api/tickets/[id]/messages/route.ts

Modifications dans POST :

// Après l'insertion du message, si !isStaff
if (!isStaff) {
  try {
    // Récupérer les infos du ticket
    const { data: ticket } = await sb.from("tickets")...
    
    // Récupérer org et code employeur
    const { data: organization } = await sb.from('organizations')...
    const { data: orgDetails } = await sb.from('organization_details')...
    
    // Récupérer les infos utilisateur avec createSbServiceRole()
    const sbAdmin = createSbServiceRole();
    const { data: userData } = await sbAdmin.auth.admin.getUserById(user.id);
    
    // Envoyer la notification
    await sendInternalTicketReplyEmail({...});
  } catch (emailError) {
    // Ne pas bloquer l'ajout du message
    console.error('Failed to send internal notification');
  }
}

🎨 Design des emails

Structure standardisée

Les deux types d'emails utilisent le système Universal Email V2 avec :

  • Header : Logo Odentas avec fond standard
  • InfoCard : Informations sur l'organisation et l'utilisateur (fond gris clair)
  • DetailsCard : Détails du ticket/message (fond blanc)
  • CTA Button : Bouton bleu "Voir le ticket"
  • Footer : Mentions légales Odentas

Couleurs

  • Header : #171424 (violet foncé standard)
  • Bouton CTA : #3B82F6 (bleu) - différent des emails clients pour distinguer les notifications internes
  • Texte bouton : #FFFFFF (blanc)

📊 Sources de données

Nom de l'utilisateur

// Priorité de récupération
userData?.user?.user_metadata?.display_name ||
userData?.user?.user_metadata?.first_name ||
'Utilisateur inconnu'

Code employeur

// Depuis organization_details
const { data: orgDetails } = await sb
  .from('organization_details')
  .select('code_employeur')
  .eq('org_id', ticket.org_id)
  .maybeSingle();

Email utilisateur

user.email || 'Email non disponible'

🔐 Permissions

  • Utilise createSbServiceRole() pour accéder à auth.admin.getUserById()
  • Les requêtes vers organizations et organization_details utilisent createSbServer() (permissions RLS normales)

⚠️ Gestion des erreurs

Important : Les erreurs d'envoi d'email ne bloquent JAMAIS la création du ticket ou l'ajout du message.

try {
  await sendInternalTicketCreatedEmail({...});
} catch (emailError) {
  // Log uniquement, ne pas throw
  console.error('Failed to send internal notification:', emailError);
}

Ceci garantit que même si AWS SES est en panne ou si les données sont incomplètes, le ticket est toujours créé/mis à jour.

📝 Logs

Les logs suivent le format standardisé :

[TICKET CREATE] Sending internal notification email...
[TICKET CREATE] Internal notification email sent successfully
[TICKET CREATE] Failed to send internal notification email: {error}
📧 [POST messages] Envoi de notification interne...
✅ [POST messages] Notification interne envoyée pour le ticket {id}
❌ [POST messages] Erreur lors de l'envoi de la notification interne: {error}

🧪 Tests suggérés

  1. Création de ticket par utilisateur :

    • Créer un ticket en tant qu'utilisateur normal
    • Vérifier que paie@odentas.fr reçoit l'email
    • Vérifier que le bouton CTA mène vers /staff/tickets/{id}
    • Vérifier que les sauts de ligne sont préservés
  2. Réponse utilisateur :

    • Répondre à un ticket en tant qu'utilisateur normal
    • Vérifier que paie@odentas.fr reçoit l'email
    • Vérifier que le statut est correct
    • Vérifier que les sauts de ligne sont préservés
  3. Pas de notification staff :

    • Créer un ticket en tant que staff → Pas d'email interne
    • Répondre en tant que staff → Email à l'utilisateur uniquement
  4. Messages internes :

    • Créer un message internal: true → Aucun email envoyé

📋 Checklist de validation

  • Template support-ticket-created ajouté à emailTemplateService.ts
  • Template support-ticket-reply ajouté à emailTemplateService.ts
  • Fonction sendInternalTicketCreatedEmail() créée
  • Fonction sendInternalTicketReplyEmail() créée
  • API POST /api/tickets modifiée pour envoyer notification
  • API POST /api/tickets/[id]/messages modifiée pour envoyer notification
  • Sauts de ligne convertis en <br> dans les deux fonctions
  • Gestion d'erreur non-bloquante implémentée
  • Utilisation de createSbServiceRole() pour getUserById
  • Code employeur récupéré depuis organization_details
  • Tous les fichiers compilent sans erreur TypeScript

🔄 Différence avec les emails clients

Fonctionnalité Email Client Email Interne
Destinataire Utilisateur créateur du ticket paie@odentas.fr
Déclencheur Staff répond (non-internal) Utilisateur crée/répond
Couleur bouton Jaune (#EFC543) Bleu (#3B82F6)
Affichage staff Nom formaté avec badge Nom utilisateur brut
URL CTA /support/{id} /staff/tickets/{id}

🚀 Déploiement

Aucune configuration supplémentaire nécessaire :

  • Les templates sont automatiquement disponibles
  • L'email paie@odentas.fr est codé en dur (pas de variable d'environnement)
  • AWS SES est déjà configuré via sendUniversalEmailV2

Date de création : 14 octobre 2025
Auteur : Système de support Odentas
Version : 1.0