, ,

Automatisez vos relances d’emails avec Google Apps Script et Sheets !

Vous en avez assez de passer un temps précieux à suivre manuellement vos emails envoyés ? D’oublier de relancer un prospect important ou un collaborateur sur un dossier crucial ? Bonne nouvelle ! Grâce à la puissance de Google Apps Script et Google Sheets, vous pouvez automatiser ce processus et vous assurer que plus aucun email important ne passe entre les mailles du filet.

Aujourd’hui, nous allons décortiquer un script brillant qui vous permet de :

  • Envoyer un premier email à une liste de contacts.
  • Envoyer une première relance automatique après 1 jour si aucune réponse n’est détectée.
  • Envoyer une dernière relance automatique après 3 jours si toujours pas de réponse.
  • Garder une trace de tous les envois et des réponses dans une feuille Google Sheets structurée.
  • Conserver toutes les communications au sein du même fil de discussion (thread) Gmail pour un suivi clair.

Que vous soyez un commercial cherchant à optimiser ses prises de contact, un chef de projet suivant l’avancement des tâches, ou simplement quelqu’un qui souhaite une meilleure organisation de ses communications, ce script est fait pour vous !

Ce que vous allez apprendre :

  • Comment fonctionne ce script d’automatisation,
  • Comment le mettre en place étape par étape dans votre propre Google Workspace,
  • Comment personnaliser les messages et les délais.

Prêt à devenir un pro de la relance automatisée ? C’est parti !

Comment ça marche ? Décryptage du script

Ce script Google Apps Script est une véritable petite merveille d’ingéniosité. Il combine plusieurs fonctionnalités de Google Workspace pour créer un système de suivi robuste.

1. Le menu personnalisé (onOpen)

Dès que vous ouvrez votre feuille Google Sheets, la fonction onOpen() s’exécute et ajoute un menu personnalisé nommé « Relances Automatiques« . Ce menu vous donne un accès facile à deux fonctions principales :

  • 1. Créer/Initialiser la feuille de suivi : Prépare votre Google Sheet.
  • 2. Envoyer les emails et relances : Lance le processus d’envoi et de suivi.
/**
 * Ajoute un menu personnalisé "Relances" dans l’interface de la feuille,
 * permettant d’accéder aux fonctions de création/initialisation et d’envoi.
 */
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Relances Automatiques') // Nom du menu mis à jour pour plus de clarté
    .addItem('1. Créer/Initialiser la feuille de suivi', 'creerTableStructuree')
    .addItem('2. Envoyer les emails et relances', 'envoyerDesEmails')
    .addToUi();
}

2. Préparation de la feuille de suivi (creerTableStructuree)

Avant de pouvoir envoyer des emails, il faut un endroit pour suivre qui a été contacté, quand, et s’ils ont répondu. C’est le rôle de la fonction creerTableStructuree().

  • Elle crée (ou réinitialise si elle existe déjà) une feuille nommée « Suivi Relances« .
  • Elle y insère un tableau structuré avec les colonnes suivantes :
    • Adresse email : Le destinataire.
    • Date du 1er envoi : Quand le premier email a été envoyé.
    • Date de réponse réelle : Quand une réponse a été détectée (ou entrée manuellement).
    • Date Relance J+1 : Quand la première relance a été envoyée.
    • Date Relance J+3 : Quand la seconde relance a été envoyée.
    • Thread ID Gmail : L’identifiant unique du fil de discussion Gmail pour ce contact.
  • Important : Cette fonction utilise l’API Google Sheets avancée. Vous devrez l’activer dans votre projet Apps Script pour qu’elle fonctionne (Services + dans l’éditeur de script).

Le formatage des dates est également pris en charge pour une lecture claire.

/**
 * Crée ou réinitialise la feuille de suivi avec une table structurée
 * pour enregistrer l'état des envois et des réponses.
 */
function creerTableStructuree() {
  const classeur = SpreadsheetApp.getActiveSpreadsheet();
  const SPREADSHEET_ID = classeur.getId();
  const nomFeuilleTable = 'Suivi Relances';

  // 1. Supprime l’onglet existant s’il y en a un, puis le recrée
  const ancienOnglet = classeur.getSheetByName(nomFeuilleTable);
  if (ancienOnglet) {
    classeur.deleteSheet(ancienOnglet);
  }
  const nouvelleFeuille = classeur.insertSheet(nomFeuilleTable);
  const ID_NOUVELLE_FEUILLE = nouvelleFeuille.getSheetId();

  // 2. Propriétés des colonnes
  const proprietesColonnes = [
    { columnIndex: 0, columnName: "Adresse email",          columnType: "TEXT"      },
    { columnIndex: 1, columnName: "Date du 1er envoi",      columnType: "DATE_TIME" },
    { columnIndex: 2, columnName: "Date de réponse réelle", columnType: "DATE_TIME" },
    { columnIndex: 3, columnName: "Date Relance J+1",       columnType: "DATE_TIME" },
    { columnIndex: 4, columnName: "Date Relance J+3",       columnType: "DATE_TIME" },
    { columnIndex: 5, columnName: "Thread ID Gmail",        columnType: "TEXT"      }
  ];

  // 3. Plage de la table (ligne d’en-tête + une ligne de données factice)
  const plageTable = {
    sheetId: ID_NOUVELLE_FEUILLE,
    startRowIndex: 0, // Commence à la première ligne pour l'en-tête
    endRowIndex: 2,   // En-tête + 1 ligne de données initiales
    startColumnIndex: 0,
    endColumnIndex: proprietesColonnes.length
  };

  // 4. Requête batch pour créer la table structurée et définir le format des dates
  const idTableUnique = 'tableSuiviRelances_' + Date.now();
  const requetes = [{
    addTable: {
      table: {
        name: nomFeuilleTable, // Nom de la table (peut être différent du nom de la feuille)
        tableId: idTableUnique,
        range: plageTable,
        columnProperties: proprietesColonnes
        // La propriété "headerRowCount: 1" a été retirée car elle causait l'erreur.
        // L'API déduit la ligne d'en-tête à partir de la plage et des columnProperties.
      }
    }
  }];

  const premiereLigneDonnees = 1; // Index 0-based pour la première ligne de données (après l'en-tête)
  const derniereLigneAFormater = nouvelleFeuille.getMaxRows();
  proprietesColonnes.forEach(colProp => {
    if (colProp.columnType === "DATE_TIME") {
      requetes.push({
        repeatCell: {
          range: {
            sheetId: ID_NOUVELLE_FEUILLE,
            startRowIndex: premiereLigneDonnees,
            endRowIndex: derniereLigneAFormater,
            startColumnIndex: colProp.columnIndex,
            endColumnIndex: colProp.columnIndex + 1
          },
          cell: {
            userEnteredFormat: {
              numberFormat: {
                type: "DATE_TIME",
                pattern: "dd/mm/yyyy HH:mm:ss"
              }
            }
          },
          fields: "userEnteredFormat.numberFormat"
        }
      });
    }
  });

  // 5. Exécution de la requête batch
  try {
    Sheets.Spreadsheets.batchUpdate({ requests: requetes }, SPREADSHEET_ID);
    const feuilleAvecTable = classeur.getSheetByName(nomFeuilleTable);
    if (feuilleAvecTable) {
      for (let i = 0; i < proprietesColonnes.length; i++) {
        feuilleAvecTable.autoResizeColumn(i + 1); // autoResizeColumn est 1-based
      }
    }
    SpreadsheetApp.getUi().alert(`Feuille "${nomFeuilleTable}" créée et formatée avec succès ! N'oubliez pas d'activer le service "Google Sheets API" dans votre projet Apps Script (Services +).`);
    Logger.log(`Table "${nomFeuilleTable}" (ID: ${idTableUnique}) créée sur la feuille ID: ${ID_NOUVELLE_FEUILLE}.`);
  } catch (erreur) {
    Logger.log('Erreur lors de la création/formatage de la table : %s', erreur.toString());
    Logger.log('Détails de l\'erreur: %s', JSON.stringify(erreur));
    // Le message d'erreur de l'utilisateur est déjà très clair, on peut le reprendre ou simplifier.
    SpreadsheetApp.getUi().alert(`Erreur lors de la création de la table: ${erreur.message}. Assurez-vous que le service "Google Sheets API" est activé et que la structure de la requête est correcte.`);
  }
}

3. Le moteur d’envoi et de suivi (envoyerDesEmails)

C’est le cœur du système ! La fonction envoyerDesEmails() parcourt chaque ligne de votre feuille « Suivi Relances » :

  1. Lecture des données : Récupère l’adresse email, les dates d’envoi/relance précédentes et le Thread ID.
  2. Détection de réponse (detecterReponseDansThread) : Si un premier email a été envoyé et qu’un Thread ID existe, le script vérifie si le contact a répondu dans ce fil de discussion après la date du dernier envoi.
  3. Logique d’envoi conditionnelle :
    • Si une réponse est détectée (ou déjà notée) : Le script passe au contact suivant. Plus aucune action n’est nécessaire.
    • Si aucun premier email n’a été envoyé : Le script envoie le premier email (avec le sujet sujetPremierEmail et le contenu HTML défini). La date d’envoi et le nouveau Thread ID sont enregistrés.
    • Si le premier email a été envoyé il y a plus d’1 jour et aucune réponse ET aucune relance J+1 envoyée : Le script envoie la première relance (avec sujetRelanceJ1) dans le même thread. La date de relance J+1 est enregistrée.
    • Si le premier email a été envoyé il y a plus de 3 jours et aucune réponse ET aucune relance J+3 envoyée : Le script envoie la dernière relance (avec sujetRelanceJ3) dans le même thread. La date de relance J+3 est enregistrée.
  4. Mise à jour de la feuille : Toutes les nouvelles dates d’envoi et les Thread IDs sont inscrits en bloc dans la feuille Google Sheets pour une efficacité maximale.

Des pauses (Utilities.sleep()) sont intégrées entre les envois pour respecter les quotas de Gmail et éviter d’être marqué comme spam.

4. Envoi dans les threads (envoyerEmailDansThread)

Pour éviter de créer une nouvelle conversation à chaque relance (ce qui est très agaçant pour le destinataire !), le script utilise la fonction envoyerEmailDansThread().

  • Si un threadIdExistant est fourni et valide, l’email est envoyé comme une réponse dans ce fil de discussion existant.
  • Sinon (pour le premier email), un nouveau thread est créé.
  • La fonction retourne l’ID du thread (nouveau ou existant) pour qu’il soit stocké dans la feuille de suivi.

5. Détection fine des réponses (detecterReponseDansThread)

C’est une partie cruciale ! La fonction detecterReponseDansThread() examine un thread Gmail spécifique :

  • Elle récupère tous les messages du thread.
  • Pour chaque message, elle vérifie :
    • Si la date du message est postérieure à la date de votre dernier envoi au contact.
    • Si l’expéditeur du message est bien l’adresse email du contact que vous suivez (et non vous-même !).
  • Si ces conditions sont remplies, elle retourne la date de la première réponse trouvée. Sinon, elle retourne null.

La fonction extraireEmailDeFrom() est un utilitaire pratique pour obtenir l’adresse email brute de l’expéditeur, même si elle est formatée comme « Nom Prénom email@exemple.com« .

6. Des emails HTML professionnels (creerCorpsEmailHtml)

Pour que vos emails aient une apparence soignée, le script utilise une fonction creerCorpsEmailHtml() qui génère le code HTML complet de l’email.

  • Elle prend en paramètres le titre de la page, le titre principal (H1), le contenu HTML spécifique à l’email, et l’année pour le pied de page.
  • Elle permet d’ajouter un bouton d’appel à l’action si besoin.
  • Le style CSS est intégré pour une compatibilité maximale avec les clients de messagerie. Vous y retrouverez un design inspiré de Google, avec la possibilité d’insérer un logo.

Vous pouvez (et devriez !) personnaliser le contenu des variables contenuHtmlPremierEmail, contenuHtmlRelanceJ1, et contenuHtmlRelanceJ3 dans la fonction envoyerDesEmails() pour adapter les messages à vos besoins. Les sujets sont aussi personnalisables via les constantes :

const sujetPremierEmail = 'Prise de contact : [Votre Objet Précis Ici]';
const sujetRelanceJ1    = 'Relance : [Votre Objet Précis Ici]';
const sujetRelanceJ3    = 'Dernière relance : [Votre Objet Précis Ici]';

Mise en place : Guide pas à pas

  1. Créez une nouvelle Feuille Google Sheets ou ouvrez une feuille existante où vous souhaitez implémenter ce système.
  2. Cliquez sur Extensions > Apps Script. Cela ouvrira l’éditeur de script.
  3. Effacez tout code existant dans le fichier Code.gs.
  4. Copiez l’intégralité du script fourni ci-dessous et collez-le dans l’éditeur Apps Script.
  5. Nommez votre projet (par exemple, « Suivi Emails Automatisé »). Cliquez sur l’icône de sauvegarde (disquette).
  6. IMPORTANT : Activez le service Google Sheets API :
    • Dans l’éditeur Apps Script, sur le côté gauche, cliquez sur le signe + à côté de « Services ».
    • Recherchez et sélectionnez « Google Sheets API« .
    • Cliquez sur « Ajouter ».
  7. Revenez à votre feuille Google Sheets. Rafraîchissez la page (F5 ou Cmd+R). Le menu « Relances automatiques » devrait apparaître.
  8. Initialisez la feuille de suivi :
    • Cliquez sur Relances Automatiques > 1. Créer/Initialiser la feuille de suivi.
    • Vous devrez probablement autoriser le script la première fois. Suivez les instructions. Choisissez votre compte Google, cliquez sur « Paramètres avancés » (ou « Advanced ») puis sur « Accéder à [Nom de votre projet] (non sécurisé) » (ou « Go to [Your Project Name] (unsafe) »). Accordez les permissions demandées.
    • Une nouvelle feuille nommée « Suivi Relances » sera créée et formatée.
  9. Ajoutez des contacts :
    • Dans la feuille « Suivi Relances », entrez les adresses email de vos destinataires dans la colonne « Adresse email » (colonne A), en commençant à la ligne 2.
  10. Lancez l’envoi :
    • Cliquez sur Relances Automatiques > 2. Envoyer les emails et relances.
    • Le script va traiter la liste. Une boîte de dialogue vous informera du nombre d’emails envoyés.
  11. Planifiez l’automatisation (Optionnel mais recommandé) :
    • Pour que le script vérifie et envoie les relances automatiquement (par exemple, tous les jours), allez dans l’éditeur Apps Script.
    • Cliquez sur l’icône « Déclencheurs » (horloge) sur la gauche.
    • Cliquez sur « + Ajouter un déclencheur » en bas à droite.
    • Configurez le déclencheur comme suit :
      • Fonction à exécuter : envoyerDesEmails
      • Source de l’événement : « Temporel » (ou « Time-driven »)
      • Type de déclencheur temporel : « Basé sur l’heure » (ou « Hour timer ») ou « Basé sur le jour » (ou « Day timer »)
      • Intervalle : Choisissez la fréquence (par exemple, « Toutes les heures » ou « Tous les jours » à une heure spécifique).
    • Cliquez sur « Enregistrer ».

Personnalisation et bonnes pratiques

  • Modifiez les sujets et les corps des emails : Adaptez les variables sujetPremierEmail, sujetRelanceJ1, sujetRelanceJ3, ainsi que les contenuHtml... dans la fonction envoyerDesEmails() pour qu’ils correspondent à votre message et à votre ton.
  • Logo et signature : Personnalisez la fonction creerCorpsEmailHtml pour inclure votre propre logo (en changeant l’URL de l’image) et les informations de votre entreprise dans le footer.
  • Délais de relance : Vous pouvez ajuster les UN_JOUR_MS et TROIS_JOURS_MS si vous souhaitez des délais différents.
  • Quotas Gmail : Soyez conscient des limites d’envoi de Gmail (environ 100-150 emails par jour pour les comptes Gmail classiques, plus pour Google Workspace). Ce script est idéal pour un suivi qualitatif plutôt que pour des campagnes de masse.
  • Testez rigoureusement : Avant de l’utiliser avec de vrais contacts, testez le script avec vos propres adresses email pour vous assurer que tout fonctionne comme prévu.
  • Vérifiez les logs : En cas de problème, les Logger.log() dans le script enregistrent des informations utiles. Vous pouvez les consulter dans l’éditeur Apps Script (Affichage > Journaux d’exécution).

Conclusion

Ce script de suivi et de relance automatisé est un excellent exemple de la manière dont Google Apps Script peut transformer votre productivité au sein de Google Workspace. En quelques étapes, vous pouvez mettre en place un système qui travaille pour vous, vous libérant du temps pour des tâches à plus forte valeur ajoutée.

N’hésitez pas à expérimenter, à personnaliser ce script et à partager vos propres astuces ou améliorations dans les commentaires ci-dessous. Quelles autres tâches répétitives aimeriez-vous automatiser avec Google Workspace ?


Le code complet à copier/coller :

/******************************************************************************
 * Nom du Projet: Suivi et Relance Emails Automatisés
 * Description: Envoie des emails de suivi et des relances automatiques
 * après 1 jour et 3 jours en l'absence de réponse.
 * Gère le suivi dans une feuille Google Sheets structurée.
 * Auteur: Fabrice Faucheux
 * Site Web: https://atelier-informatique.com
 * Date de mise à jour: 31/05/2025
 ******************************************************************************/

/**
 * @OnlyCurrentDoc
 */

  const sujetPremierEmail   = 'Prise de contact : [Votre Objet Précis Ici]';
  const sujetRelanceJ1      = 'Relance : [Votre Objet Précis Ici]';
  const sujetRelanceJ3      = 'Dernière relance : [Votre Objet Précis Ici]';

/**
 * Ajoute un menu personnalisé "Relances" dans l’interface de la feuille,
 * permettant d’accéder aux fonctions de création/initialisation et d’envoi.
 */
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Relances Automatiques') // Nom du menu mis à jour pour plus de clarté
    .addItem('1. Créer/Initialiser la feuille de suivi', 'creerTableStructuree')
    .addItem('2. Envoyer les emails et relances', 'envoyerDesEmails')
    .addToUi();
}

/**
 * Crée ou réinitialise la feuille de suivi avec une table structurée
 * pour enregistrer l'état des envois et des réponses.
 */
function creerTableStructuree() {
  const classeur = SpreadsheetApp.getActiveSpreadsheet();
  const SPREADSHEET_ID = classeur.getId();
  const nomFeuilleTable = 'Suivi Relances';

  // 1. Supprime l’onglet existant s’il y en a un, puis le recrée
  const ancienOnglet = classeur.getSheetByName(nomFeuilleTable);
  if (ancienOnglet) {
    classeur.deleteSheet(ancienOnglet);
  }
  const nouvelleFeuille = classeur.insertSheet(nomFeuilleTable);
  const ID_NOUVELLE_FEUILLE = nouvelleFeuille.getSheetId();

  // 2. Propriétés des colonnes
  const proprietesColonnes = [
    { columnIndex: 0, columnName: "Adresse email",          columnType: "TEXT"      },
    { columnIndex: 1, columnName: "Date du 1er envoi",      columnType: "DATE_TIME" },
    { columnIndex: 2, columnName: "Date de réponse réelle", columnType: "DATE_TIME" },
    { columnIndex: 3, columnName: "Date Relance J+1",       columnType: "DATE_TIME" },
    { columnIndex: 4, columnName: "Date Relance J+3",       columnType: "DATE_TIME" },
    { columnIndex: 5, columnName: "Thread ID Gmail",        columnType: "TEXT"      }
  ];

  // 3. Plage de la table (ligne d’en-tête + une ligne de données factice)
  const plageTable = {
    sheetId: ID_NOUVELLE_FEUILLE,
    startRowIndex: 0, // Commence à la première ligne pour l'en-tête
    endRowIndex: 2,   // En-tête + 1 ligne de données initiales
    startColumnIndex: 0,
    endColumnIndex: proprietesColonnes.length
  };

  // 4. Requête batch pour créer la table structurée et définir le format des dates
  const idTableUnique = 'tableSuiviRelances_' + Date.now();
  const requetes = [{
    addTable: {
      table: {
        name: nomFeuilleTable, // Nom de la table (peut être différent du nom de la feuille)
        tableId: idTableUnique,
        range: plageTable,
        columnProperties: proprietesColonnes
        // La propriété "headerRowCount: 1" a été retirée car elle causait l'erreur.
        // L'API déduit la ligne d'en-tête à partir de la plage et des columnProperties.
      }
    }
  }];

  const premiereLigneDonnees = 1; // Index 0-based pour la première ligne de données (après l'en-tête)
  const derniereLigneAFormater = nouvelleFeuille.getMaxRows();
  proprietesColonnes.forEach(colProp => {
    if (colProp.columnType === "DATE_TIME") {
      requetes.push({
        repeatCell: {
          range: {
            sheetId: ID_NOUVELLE_FEUILLE,
            startRowIndex: premiereLigneDonnees,
            endRowIndex: derniereLigneAFormater,
            startColumnIndex: colProp.columnIndex,
            endColumnIndex: colProp.columnIndex + 1
          },
          cell: {
            userEnteredFormat: {
              numberFormat: {
                type: "DATE_TIME",
                pattern: "dd/mm/yyyy HH:mm:ss"
              }
            }
          },
          fields: "userEnteredFormat.numberFormat"
        }
      });
    }
  });

  // 5. Exécution de la requête batch
  try {
    Sheets.Spreadsheets.batchUpdate({ requests: requetes }, SPREADSHEET_ID);
    const feuilleAvecTable = classeur.getSheetByName(nomFeuilleTable);
    if (feuilleAvecTable) {
      for (let i = 0; i < proprietesColonnes.length; i++) {
        feuilleAvecTable.autoResizeColumn(i + 1); // autoResizeColumn est 1-based
      }
    }
    SpreadsheetApp.getUi().alert(`Feuille "${nomFeuilleTable}" créée et formatée avec succès ! N'oubliez pas d'activer le service "Google Sheets API" dans votre projet Apps Script (Services +).`);
    Logger.log(`Table "${nomFeuilleTable}" (ID: ${idTableUnique}) créée sur la feuille ID: ${ID_NOUVELLE_FEUILLE}.`);
  } catch (erreur) {
    Logger.log('Erreur lors de la création/formatage de la table : %s', erreur.toString());
    Logger.log('Détails de l\'erreur: %s', JSON.stringify(erreur));
    // Le message d'erreur de l'utilisateur est déjà très clair, on peut le reprendre ou simplifier.
    SpreadsheetApp.getUi().alert(`Erreur lors de la création de la table: ${erreur.message}. Assurez-vous que le service "Google Sheets API" est activé et que la structure de la requête est correcte.`);
  }
}

/**
 * Envoie un email via GmailApp. Si un threadId est fourni et valide,
 * l'email est envoyé comme une réponse dans ce thread. Sinon, un nouveau thread est créé.
 * Retourne le threadId du mail envoyé (existant ou nouveau).
 *
 * @param {string} adresseEmail  L’adresse du destinataire.
 * @param {string} sujet         Le sujet de l’email.
 * @param {string} corpsHtml     Le corps HTML de l’email.
 * @param {string|null} threadIdExistant Le threadId existant, ou null pour un nouveau thread.
 * @param {object} optionsSuppl Options supplémentaires pour GmailApp.sendEmail (ex: cc, bcc).
 * @returns {string|null}        Le threadId du message envoyé, ou null en cas d’erreur.
 */
function envoyerEmailDansThread(adresseEmail, sujet, corpsHtml, threadIdExistant, optionsSuppl = {}) {
  try {
    let optionsFinales = { ...optionsSuppl, htmlBody: corpsHtml }; // Toujours utiliser htmlBody pour plus de flexibilité

    if (threadIdExistant) {
      // Vérifier si le thread existe avant de tenter de l'utiliser
      try {
        const thread = GmailApp.getThreadById(threadIdExistant);
        if (thread) {
          optionsFinales.threadId = threadIdExistant;
          // GmailApp.sendEmail avec un threadId permet de spécifier un nouveau sujet,
          // contrairement à thread.reply() qui reprend le sujet du message précédent.
        } else {
          Logger.log(`Thread ID ${threadIdExistant} non trouvé. Un nouveau thread sera créé pour ${adresseEmail}.`);
          // threadIdExistant est invalide, ne pas le mettre dans les options.
        }
      } catch (e) {
        Logger.log(`Erreur lors de la vérification du Thread ID ${threadIdExistant}: ${e}. Un nouveau thread sera créé.`);
        // Erreur d'accès au thread, ne pas le mettre dans les options.
      }
    }

    GmailApp.sendEmail(adresseEmail, sujet, "", optionsFinales); // Le corps texte simple est vide, car on utilise htmlBody.

    // Si un threadId était déjà fourni et utilisé (et valide), on le retourne.
    if (optionsFinales.threadId) {
      return optionsFinales.threadId;
    }

    // Si un nouveau thread a été créé, il faut le retrouver.
    // Attendre un court instant pour l'indexation par Gmail.
    Utilities.sleep(2500); // Légère augmentation pour la robustesse
    // Recherche plus spécifique: on peut ajouter 'is:sent' et 'newer_than:1m' si besoin.
    const requeteRecherche = `to:"${adresseEmail}" subject:"${sujet}" in:sent`;
    const threadsTrouves = GmailApp.search(requeteRecherche, 0, 1); // On prend le plus récent

    if (threadsTrouves && threadsTrouves.length > 0) {
      const nouveauThreadId = threadsTrouves[0].getId();
      Logger.log(`Email envoyé à ${adresseEmail}, nouveau thread ID: ${nouveauThreadId}`);
      return nouveauThreadId;
    } else {
      Logger.log(`Impossible de retrouver le thread pour l'email envoyé à ${adresseEmail} (sujet: "${sujet}"). La recherche n'a rien donné.`);
      return null;
    }
  } catch (e) {
    Logger.log(`Erreur lors de l'envoi de l'email à ${adresseEmail}: ${e.toString()}`);
    Logger.log(`Détails de l'erreur: Sujet='${sujet}', Options=${JSON.stringify(optionsSuppl)}`);
    return null;
  }
}

/**
 * Parcourt chaque contact listé dans la feuille 'Suivi Relances'.
 * 1. Vérifie si une réponse a été reçue dans le thread Gmail associé.
 * 2. Si aucune réponse :
 * - Envoie un 1er email si aucun n'a été envoyé.
 * - Envoie une 1ère relance (J+1) si > 1 jour depuis le 1er envoi et pas encore envoyée.
 * - Envoie une 2nde relance (J+3) si > 3 jours depuis le 1er envoi et pas encore envoyée.
 * 3. Met à jour les dates d'envoi, de réponse et le Thread ID dans la feuille.
 */
function envoyerDesEmails() {
  const classeur = SpreadsheetApp.getActiveSpreadsheet();
  const nomFeuille = 'Suivi Relances';
  const feuille = classeur.getSheetByName(nomFeuille);

  const anneeActuelle = new Date().getFullYear();

  if (!feuille) {
    SpreadsheetApp.getUi().alert(`L’onglet "${nomFeuille}" n’existe pas. Veuillez d'abord le créer via le menu.`);
    return;
  }

  const derniereLigne = feuille.getLastRow();
  // Commencer à la ligne 2 pour exclure l'en-tête
  if (derniereLigne < 2) {
    SpreadsheetApp.getUi().alert(`Aucune donnée de contact trouvée dans l'onglet "${nomFeuille}".`);
    return;
  }
  const nombreLignesDonnees = derniereLigne - 1;

  // === Lecture en bloc des données des colonnes ===
  // Les indices de colonnes pour getRange sont 1-based.
  const plageDonnees = feuille.getRange(2, 1, nombreLignesDonnees, 6).getValues(); // Lire les 6 colonnes

    // === Contenus HTML spécifiques et Titres H1 pour chaque email ===

    // -- Premier Email --
    // Le titre H1 peut être différent du sujet de l'email si vous le souhaitez.
    const titreH1PremierEmail = "Informations importantes concernant votre dossier";
    // Personnalisez le contenu. Vous pouvez y insérer des variables (ex: nom du contact, détails spécifiques)
    // si vous ajoutez une colonne "Nom" à votre feuille et la lisez.
    // Pour l'exemple, je garde un contenu générique.
    const contenuHtmlPremierEmail = `
      <p>Bonjour,</p>
      <p>Nous vous contactons concernant [détail de la raison du contact]. Nous aimerions attirer votre attention sur [point important].</p>
      <p>N'hésitez pas à nous faire part de vos questions ou commentaires.</p>
      <p>Cordialement,<br>L'équipe [Votre Nom/Service]</p>
    `;
    // Générer le corps HTML complet
    const corpsPremierEmailComplet = creerCorpsEmailHtml(
      sujetPremierEmail,        // Titre de la page (peut être le sujet)
      titreH1PremierEmail,
      contenuHtmlPremierEmail,
      anneeActuelle
      // Si vous voulez un bouton pour cet email :
      // "Cliquez ici", "https://votre-lien.com"
    );

    // -- Relance J+1 --
    const titreH1RelanceJ1 = "Rappel amical";
    const contenuHtmlRelanceJ1 = `
      <p>Bonjour,</p>
      <p>Pour faire suite à notre précédent message, nous souhaitions simplement nous assurer que vous aviez bien reçu nos informations concernant [détail de la raison du contact].</p>
      <p>Nous restons à votre disposition pour toute question.</p>
      <p>Cordialement,<br>L'équipe [Votre Nom/Service]</p>
    `;
    const corpsRelanceJ1Complet = creerCorpsEmailHtml(
      sujetRelanceJ1,
      titreH1RelanceJ1,
      contenuHtmlRelanceJ1,
      anneeActuelle
    );

    // -- Relance J+3 --
    const titreH1RelanceJ3 = "Concernant notre précédente communication";
    const contenuHtmlRelanceJ3 = `
      <p>Bonjour,</p>
      <p>Sauf erreur de notre part, nous n'avons pas encore eu de retour concernant [détail de la raison du contact].</p>
      <p>Si ce sujet n'est plus d'actualité pour vous, ou si vous avez besoin de plus de temps, n'hésitez pas à nous en informer.</p>
      <p>Cordialement,<br>L'équipe [Votre Nom/Service]</p>
    `;
    const corpsRelanceJ3Complet = creerCorpsEmailHtml(
      sujetRelanceJ3,
      titreH1RelanceJ3,
      contenuHtmlRelanceJ3,
      anneeActuelle
    );

  // === Délais en millisecondes ===
  const UN_JOUR_MS     = 1 * 24 * 60 * 60 * 1000;
  const TROIS_JOURS_MS = 3 * 24 * 60 * 60 * 1000;

  let compteurEnvois = 0;

  // Tableaux pour stocker les mises à jour à écrire en fin de script
  let majDatesPremierEnvoi = [];
  let majDatesReponse = [];
  let majDatesRelanceJ1 = [];
  let majDatesRelanceJ3 = [];
  let majThreadIds = [];

  // === Parcours des contacts ===
  for (let i = 0; i < nombreLignesDonnees; i++) {
    const ligneActuelle = plageDonnees[i];
    const adresseEmail       = ligneActuelle[0]; // Colonne A (index 0)
    // Conversion en objets Date ou null pour une manipulation aisée
    let datePremierEnvoi     = ligneActuelle[1] ? new Date(ligneActuelle[1]) : null; // Colonne B (index 1)
    let dateReponse          = ligneActuelle[2] ? new Date(ligneActuelle[2]) : null; // Colonne C (index 2)
    let dateRelanceJ1        = ligneActuelle[3] ? new Date(ligneActuelle[3]) : null; // Colonne D (index 3)
    let dateRelanceJ3        = ligneActuelle[4] ? new Date(ligneActuelle[4]) : null; // Colonne E (index 4)
    let threadId             = ligneActuelle[5] ? ligneActuelle[5].toString().trim() : null; // Colonne F (index 5)

    // Si pas d’adresse email valide, on saute cette ligne
    if (!adresseEmail || typeof adresseEmail !== 'string' || adresseEmail.trim() === "" || adresseEmail.indexOf('@') === -1) {
      // Conserver les valeurs existantes pour l'écriture batch
      majDatesPremierEnvoi.push([ligneActuelle[1] || '']);
      majDatesReponse.push([ligneActuelle[2] || '']);
      majDatesRelanceJ1.push([ligneActuelle[3] || '']);
      majDatesRelanceJ3.push([ligneActuelle[4] || '']);
      majThreadIds.push([ligneActuelle[5] || '']);
      continue;
    }

    const maintenant = new Date();
    const maintenantTime = maintenant.getTime();

    // 1) Détection automatique de réponse
    // On vérifie la réponse seulement si un premier email a été envoyé et qu'il n'y a pas encore de réponse enregistrée.
    if (datePremierEnvoi && threadId && !dateReponse) {
      // Utiliser la date la plus récente entre le premier envoi et la dernière relance J1 (si envoyée) comme référence.
      let dateReferencePourReponse = datePremierEnvoi;
      if (dateRelanceJ1 && dateRelanceJ1 > datePremierEnvoi) {
        dateReferencePourReponse = dateRelanceJ1;
      }
      const dateReponseDetectee = detecterReponseDansThread(adresseEmail, dateReferencePourReponse, threadId);
      if (dateReponseDetectee) {
        dateReponse = dateReponseDetectee; // Mettre à jour la variable locale
      }
    }

    // Si une réponse est enregistrée (manuellement ou auto-détectée), on ne fait plus rien pour ce contact.
    if (dateReponse) {
      majDatesPremierEnvoi.push([datePremierEnvoi || '']);
      majDatesReponse.push([dateReponse]); // Mettre à jour la date de réponse
      majDatesRelanceJ1.push([dateRelanceJ1 || '']);
      majDatesRelanceJ3.push([dateRelanceJ3 || '']);
      majThreadIds.push([threadId || '']);
      continue;
    }

    // 2) Premier envoi si jamais envoyé
    if (!datePremierEnvoi) {
      Logger.log(`Préparation du 1er envoi pour ${adresseEmail}`);
      const nouveauThreadId = envoyerEmailDansThread(adresseEmail, sujetPremierEmail, corpsPremierEmailComplet, null);
      if (nouveauThreadId) {
        threadId = nouveauThreadId;
        datePremierEnvoi = new Date(maintenantTime); // Stocker la date actuelle de l'envoi
        compteurEnvois++;
        Utilities.sleep(1500); // Pause pour éviter de dépasser les quotas Gmail
      } else {
        Logger.log(`Échec de l'envoi initial ou de la récupération du ThreadID pour ${adresseEmail}.`);
      }
    }
    // Si le premier envoi vient d'être fait (ou a échoué mais qu'on ne veut pas relancer dans la même passe)
    // on passe au contact suivant pour cette exécution du script. Les relances seront gérées à la prochaine exécution.
    // Ceci évite d'envoyer une relance J+1 immédiatement si le script tourne très vite.
    // Cependant, si le premier envoi a été fait lors d'une exécution précédente, on continue pour vérifier les relances.
    // La condition ci-dessous s'assure que `datePremierEnvoi` est défini pour les étapes suivantes.
    
    // Si après la tentative d'envoi initial, datePremierEnvoi n'est toujours pas défini (échec critique)
    if (!datePremierEnvoi) {
        majDatesPremierEnvoi.push(['']); // Pas de date de premier envoi
        majDatesReponse.push(['']);
        majDatesRelanceJ1.push(['']);
        majDatesRelanceJ3.push(['']);
        majThreadIds.push([threadId || '']); // threadId peut être null si échec
        continue;
    }


    // À ce stade: le premier email a été envoyé (datePremierEnvoi est non nul), et aucune réponse n'a été reçue.
    // On peut maintenant vérifier les conditions de relance.

    // 3) Vérifier pour la Relance J+1
    // Condition: > 1 jour depuis le premier envoi ET la relance J+1 n'a pas encore été envoyée.
    if (!dateRelanceJ1 && (maintenantTime - datePremierEnvoi.getTime()) > UN_JOUR_MS) {
      Logger.log(`Préparation Relance J+1 pour ${adresseEmail}`);
      if (threadId) { // S'assurer qu'on a un threadId pour la relance
         const threadRelance1 = envoyerEmailDansThread(adresseEmail, sujetRelanceJ1, corpsRelanceJ1Complet, threadId);
         // ... (reste de la logique de la relance J+1) ...
         if (threadRelance1) { // Si l'envoi réussit (même si threadId n'a pas changé)
            dateRelanceJ1 = new Date(maintenantTime);
            compteurEnvois++;
            Utilities.sleep(1500);
         } else {
            Logger.log(`Échec de la Relance J+1 pour ${adresseEmail}.`);
         }
      } else {
        Logger.log(`ThreadId manquant pour Relance J+1 pour ${adresseEmail}. Relance non envoyée.`);
      }
    }

    // 4) Vérifier pour la Relance J+3
    // Condition: > 3 jours depuis le premier envoi ET la relance J+3 n'a pas encore été envoyée.
    // Et aussi, on s'assure que la relance J+1 a eu lieu (ou que son délai est bien passé,
    // pour ne pas envoyer J+3 avant J+1 si le script n'a pas tourné entre J+1 et J+3).
    // Le `if (!dateRelanceJ3)` gère déjà le "pas encore envoyée".
    // La condition `(maintenantTime - datePremierEnvoi.getTime()) > TROIS_JOURS_MS` est la principale.
    if (!dateRelanceJ3 && (maintenantTime - datePremierEnvoi.getTime()) > TROIS_JOURS_MS) {
      // On vérifie aussi si la relance J+1 a été envoyée. Si non, et qu'on est à J+3, c'est un peu étrange.
      // Mais le besoin est "après trois jours relancer".
      // Si J+1 n'a pas été envoyée (par exemple, script n'a pas tourné), on envoie quand même J+3 si due.
      Logger.log(`Préparation Relance J+3 pour ${adresseEmail}`);
      if (threadId) {
        const threadRelance3 = envoyerEmailDansThread(adresseEmail, sujetRelanceJ3, corpsRelanceJ3Complet, threadId);
        if (threadRelance3) {
            dateRelanceJ3 = new Date(maintenantTime);
            compteurEnvois++;
            Utilities.sleep(1500);
        } else {
            Logger.log(`Échec de la Relance J+3 pour ${adresseEmail}.`);
        }
      } else {
        Logger.log(`ThreadId manquant pour Relance J+3 pour ${adresseEmail}. Relance non envoyée.`);
      }
    }

    // Stocker les valeurs (originales ou mises à jour) pour l'écriture batch
    majDatesPremierEnvoi.push([datePremierEnvoi instanceof Date ? datePremierEnvoi : '']);
    majDatesReponse.push([dateReponse instanceof Date ? dateReponse : '']);
    majDatesRelanceJ1.push([dateRelanceJ1 instanceof Date ? dateRelanceJ1 : '']);
    majDatesRelanceJ3.push([dateRelanceJ3 instanceof Date ? dateRelanceJ3 : '']);
    majThreadIds.push([threadId || '']);

  } // Fin de la boucle for sur les contacts

  // === Écriture en bloc des mises à jour dans la feuille ===
  // S'assurer que les tableaux de mise à jour ont la bonne dimension
  if (nombreLignesDonnees > 0) {
    feuille.getRange(2, 2, nombreLignesDonnees, 1).setValues(majDatesPremierEnvoi);
    feuille.getRange(2, 3, nombreLignesDonnees, 1).setValues(majDatesReponse);
    feuille.getRange(2, 4, nombreLignesDonnees, 1).setValues(majDatesRelanceJ1); // Nouvelle colonne
    feuille.getRange(2, 5, nombreLignesDonnees, 1).setValues(majDatesRelanceJ3);
    feuille.getRange(2, 6, nombreLignesDonnees, 1).setValues(majThreadIds);
  }

  Logger.log(`Traitement des emails terminé. ${compteurEnvois} email(s) envoyé(s) lors de cette exécution.`);
  if (compteurEnvois > 0) {
    SpreadsheetApp.getUi().alert(`${compteurEnvois} email(s) envoyé(s) / relance(s) effectuée(s).`);
  } else {
    SpreadsheetApp.getUi().alert('Traitement des emails terminé. Aucun nouvel email ou relance à envoyer pour le moment.');
  }
}


/**
 * Fonction utilitaire pour extraire l'adresse email d'un en-tête "From" standard.
 * Exemple: "Nom Prénom <email@example.com>" deviendra "email@example.com".
 * @param {string} fromHeader La chaîne de l'en-tête "From".
 * @returns {string} L'adresse email extraite en minuscules, ou la chaîne originale en minuscules si le format n'est pas reconnu.
 */
function extraireEmailDeFrom(fromHeader) {
  if (!fromHeader || typeof fromHeader !== 'string') return "";
  const match = fromHeader.match(/<([^>]+)>/);
  return match && match[1] ? match[1].toLowerCase() : fromHeader.toLowerCase();
}

/**
 * Détecte si un contact a répondu DANS LE MÊME THREAD Gmail après la date dateReferenceEnvoi.
 * VERSION AVEC LOGS DE DÉBOGAGE DÉTAILLÉS.
 *
 * @param {string} partieAdresseEmail     L’adresse email EXACTE du contact attendu pour la réponse.
 * @param {Date}   dateReferenceEnvoi     La date du dernier envoi pertinent pour ce contact.
 * @param {string} threadId               Le threadId Gmail de la conversation.
 * @returns {Date|null}                   La date de la première réponse détectée, ou null.
 */
function detecterReponseDansThread(partieAdresseEmail, dateReferenceEnvoi, threadId) { // Renommée pour correspondre à l'appelant
  Logger.log(`--- detecterReponseDansThread DÉBUT ---`);
  Logger.log(`Paramètres reçus :`);
  Logger.log(`  partieAdresseEmail (contact): ${partieAdresseEmail}`);
  Logger.log(`  dateReferenceEnvoi: ${dateReferenceEnvoi} (Type: ${typeof dateReferenceEnvoi}, Est Date valide: ${dateReferenceEnvoi instanceof Date && !isNaN(dateReferenceEnvoi)})`);
  Logger.log(`  threadId: ${threadId}`);

  // Vérification stricte des paramètres
  if (!partieAdresseEmail || typeof partieAdresseEmail !== 'string' || partieAdresseEmail.trim() === '' ||
      !(dateReferenceEnvoi instanceof Date && !isNaN(dateReferenceEnvoi)) ||
      !threadId || typeof threadId !== 'string' || threadId.trim() === '') {
    Logger.log(`ERREUR: Paramètres manquants ou invalides. Arrêt de la détection.`);
    Logger.log(`--- detecterReponseDansThread FIN (Paramètres invalides) ---`);
    return null;
  }
  const emailContactCible = partieAdresseEmail.toLowerCase().trim(); // Normaliser l'email cible

  try {
    const thread = GmailApp.getThreadById(threadId);
    if (!thread) {
      Logger.log(`Thread ${threadId} non trouvé pour le contact ${emailContactCible}.`);
      Logger.log(`--- detecterReponseDansThread FIN (Thread non trouvé) ---`);
      return null;
    }
    Logger.log(`Thread "${thread.getFirstMessageSubject()}" trouvé (ID: ${threadId}). Nb messages: ${thread.getMessageCount()}`);

    let datePremiereReponseTrouvee = null;
    const messages = thread.getMessages();
    const emailUtilisateurScript = Session.getActiveUser().getEmail().toLowerCase();
    Logger.log(`Email de l'utilisateur du script (sera ignoré comme expéditeur de réponse): ${emailUtilisateurScript}`);

    for (let i = 0; i < messages.length; i++) {
      const message = messages[i];
      const dateMessage = message.getDate();
      const expediteurMessageComplet = message.getFrom();
      const expediteurMessageEmailOnly = extraireEmailDeFrom(expediteurMessageComplet); // Utilise la fonction utilitaire
      const sujetMessage = message.getSubject();

      Logger.log(`  Analyse Message ${i + 1}/${messages.length}: Sujet: "${sujetMessage}"`);
      Logger.log(`    Date du message: ${dateMessage}`);
      Logger.log(`    Expéditeur (brut): "${expediteurMessageComplet}"`);
      Logger.log(`    Expéditeur (email extrait): "${expediteurMessageEmailOnly}"`);

      if (dateMessage.getTime() > dateReferenceEnvoi.getTime()) { // Comparaison des millisecondes pour plus de sûreté
        Logger.log(`    -> Ce message (${dateMessage}) EST APRES dateReferenceEnvoi (${dateReferenceEnvoi}).`);

        // Comparaison stricte des adresses email extraites
        if (expediteurMessageEmailOnly === emailContactCible) {
          Logger.log(`    --> L'expéditeur (${expediteurMessageEmailOnly}) CORRESPOND à l'email du contact cible (${emailContactCible}).`);

          if (expediteurMessageEmailOnly !== emailUtilisateurScript) {
            Logger.log(`    ---> Ce message N'EST PAS de l'utilisateur du script (${emailUtilisateurScript}). C'est une réponse valide potentielle.`);
            if (datePremiereReponseTrouvee === null || dateMessage.getTime() < datePremiereReponseTrouvee.getTime()) {
              datePremiereReponseTrouvee = dateMessage;
              Logger.log(`    !!!!! Nouvelle date de PREMIÈRE réponse trouvée: ${datePremiereReponseTrouvee} !!!!!`);
            } else {
              Logger.log(`    Une réponse antérieure a déjà été trouvée (${datePremiereReponseTrouvee}).`);
            }
          } else {
            Logger.log(`    ---> Ce message EST de l'utilisateur du script (${emailUtilisateurScript}). Ignoré comme réponse d'un tiers.`);
          }
        } else {
          Logger.log(`    --> L'expéditeur (${expediteurMessageEmailOnly}) NE CORRESPOND PAS à l'email du contact cible (${emailContactCible}).`);
        }
      } else {
        Logger.log(`    -> Ce message (${dateMessage}) EST AVANT ou EGAL à dateReferenceEnvoi (${dateReferenceEnvoi}). Ignoré.`);
      }
    } // Fin de la boucle sur les messages

    if (datePremiereReponseTrouvee) {
      Logger.log(`RÉSUMÉ: Réponse finale détectée de ${emailContactCible} le ${datePremiereReponseTrouvee}.`);
    } else {
      Logger.log(`RÉSUMÉ: Aucune nouvelle réponse valide de ${emailContactCible} (après ${dateReferenceEnvoi}) trouvée dans le thread ${threadId}.`);
    }
    Logger.log(`--- detecterReponseDansThread FIN ---`);
    return datePremiereReponseTrouvee;
  } catch (e) {
    Logger.log(`ERREUR INATTENDUE lors de la détection de réponse pour ${emailContactCible} (thread ${threadId}): ${e.message} ${e.stack ? '\nStack: ' + e.stack : ''}`);
    Logger.log(`--- detecterReponseDansThread FIN (Erreur inattendue) ---`);
    return null;
  }
}

/**
 * Crée le corps HTML complet d'un email en utilisant un template stylisé.
 * @param {string} titrePage Le titre pour la balise <title> du HTML.
 * @param {string} titrePrincipalH1 Le titre principal affiché dans l'email (balise H1).
 * @param {string} contenuHtmlSpecifique Le contenu HTML spécifique au message.
 * @param {string|number} annee L'année en cours pour le footer.
 * @param {string} [texteBouton] Optionnel. Le texte à afficher sur un bouton.
 * @param {string} [urlBouton] Optionnel. L'URL de destination du bouton.
 * @return {string} La chaîne HTML complète du corps de l'email.
 */
function creerCorpsEmailHtml(titrePage, titrePrincipalH1, contenuHtmlSpecifique, annee, texteBouton, urlBouton) {
  let boutonHtml = '';
  if (texteBouton && urlBouton) {
    boutonHtml = `
      <div class="button-container">
        <a href="${urlBouton}" class="button">${texteBouton}</a>
      </div>
    `;
  }

  // Le logo et la structure générale sont basés sur votre template.
  // Les styles CSS que vous avez fournis sont inclus directement.
  return `
    <!DOCTYPE html>
    <html lang="fr">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>${titrePage}</title>
      <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
      <style>
        body {
          font-family: 'Roboto', Arial, sans-serif; margin: 0; padding: 0; background-color: #f8f9fa;
          color: #202124; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
        }
        .email-wrapper { padding: 20px; background-color: #f8f9fa; }
        .container {
          background-color: #ffffff; border: 1px solid #dadce0; padding: 32px; border-radius: 8px;
          max-width: 600px; margin: 0 auto;
          box-shadow: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);
        }
        .header { padding-bottom: 16px; margin-bottom: 24px; border-bottom: 1px solid #e0e0e0; text-align: left; }
        .logo-container { margin-bottom: 16px; }
        .logo-container img { display: block; height: 24px; width: auto; }
        .header h1 { font-size: 22px; font-weight: 500; color: #202124; margin: 0; line-height: 1.3; }
        .content { margin-top: 0; line-height: 1.6; font-size: 14px; color: #3c4043; }
        .content p { margin: 0 0 16px 0; }
        .content p:last-child { margin-bottom: 0; }
        .content strong { color: #202124; }
        .button-container { margin-top: 24px; margin-bottom: 24px; text-align: left; }
        .button {
          background-color: #1a73e8; color: #ffffff !important; padding: 10px 24px;
          text-decoration: none !important; border-radius: 4px; font-weight: 500; font-size: 14px;
          display: inline-block; border: none; cursor: pointer;
        }
        .button:hover { background-color: #1765cc; }
        .content table {
          width: 100%; border-collapse: collapse; margin-top: 16px; margin-bottom: 16px; font-size: 14px;
        }
        .content th, .content td {
          text-align: left; padding: 10px 12px; border: 1px solid #e0e0e0; vertical-align: top;
        }
        .content th { font-weight: 500; color: #202124; background-color: #f8f9fa; }
        .footer {
          margin-top: 32px; padding-top: 16px; border-top: 1px solid #e0e0e0;
          font-size: 12px; color: #5f6368; text-align: center; line-height: 1.5;
        }
        .footer a { color: #5f6368; text-decoration: underline; }
        .footer a:hover { color: #1a73e8; text-decoration: underline; }
        img { border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
        table, td { border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; }
      </style>
    </head>
    <body>
      <div class="email-wrapper">
        <div class="container">
          <div class="header">
            <div class="logo-container">
              <img src="https://workspace.google.com/static/img/google-workspace-logo.png" alt="Google Workspace Logo" />
            </div>
            <h1>${titrePrincipalH1}</h1>
          </div>
          <div class="content">
            ${contenuHtmlSpecifique}
          </div>
          ${boutonHtml}
          <div class="footer">
            <p>Cet e-mail a été envoyé automatiquement. Merci de ne pas y répondre directement.</p>
            <p>&copy; ${annee} Votre Entreprise/Organisation.</p>
          </div>
        </div>
      </div>
    </body>
    </html>
  `;
}