Révolutionnez votre boîte mail avec le répondeur automatique intelligent propulsé par Gemini !

Marre des mails qui s’accumulent et des réponses répétitives ? Imaginez un assistant capable de comprendre le contenu de vos e-mails, de les résumer et de générer des réponses personnalisées en quelques secondes. Ce n’est plus de la science-fiction ! Aujourd’hui, je suis ravi de vous présenter suite à un post de l’excellent Stéphane Giron un répondeur automatique intelligent que j’ai développé en utilisant Google Apps Script et l’incroyable puissance de l’IA Gemini de Google.

Ce script transforme votre boîte de réception Gmail en un centre de communication ultra-efficace, vous libérant du temps précieux pour vous concentrer sur ce qui compte vraiment.

Pourquoi un répondeur intelligent ? L’intérêt du script

Dans le monde numérique actuel, nous sommes constamment sollicités par des e-mails. Gérer cette charge peut devenir une tâche chronophage et répétitive. C’est là qu’intervient l’intérêt d’un répondeur intelligent :

  • Gain de temps considérable : Le script prend en charge la première interaction, vous évitant de devoir répondre manuellement aux requêtes initiales.
  • Réponses instantanées 24/7 : Vos correspondants reçoivent une réponse rapide, même en dehors de vos heures de travail.
  • Personnalisation avancée : Contrairement aux répondeurs automatiques classiques, celui-ci ne se contente pas d’un message générique. Grâce à Gemini, il analyse le contenu de chaque e-mail pour générer un résumé pertinent et une réponse adaptée.
  • Optimisation de la communication : Les réponses sont claires et concises, souvent capables de demander des informations complémentaires si nécessaire, rendant les échanges futurs plus efficaces.
  • Filtrage intelligent : Vous pouvez exclure certains expéditeurs (comme les newsletters ou des adresses internes) pour ne traiter que les e-mails pertinents.

Comment ça marche sous le capot ?

Ce répondeur s’articule autour de deux piliers technologiques : Google Apps Script pour l’automatisation dans Gmail, et l’API Gemini pour l’intelligence artificielle.

La magie de Google Apps Script

Le script fonctionne en arrière-plan, déclenché toutes les 5 minutes (cette fréquence est configurable). Voici les étapes clés :

  1. Configuration initiale (configurerRepondeurIntelligent) : Cette fonction prépare le terrain en créant un libellé Gmail spécifique (« Réponse intelligente ») pour marquer les e-mails traités. Elle configure également un déclencheur automatique pour exécuter la fonction principale régulièrement.
  2. Gestionnaire principal (gererRepondeurPrincipal) :
    • Il recherche les e-mails non lus qui n’ont pas encore été traités par le répondeur (ceux qui n’ont pas le libellé « Réponse intelligente »).
    • Pour chaque nouvel e-mail, il vérifie si l’expéditeur est sur une liste d’exclusion. Si c’est le cas, l’e-mail est simplement marqué comme lu et labellisé.
    • Si l’e-mail doit être traité, le script extrait le sujet et le corps du message.
    • Ces informations sont ensuite envoyées à l’API Gemini pour générer une réponse intelligente.
    • Enfin, il envoie la réponse formatée à l’expéditeur, marque l’e-mail comme lu et lui attribue le libellé.

L’intelligence de Gemini : Appel et Réponse

C’est ici que l’IA entre en jeu. La fonction genererReponseAvecGemini est le cœur de l’intelligence du répondeur :

  1. Préparation de la Requête (le « Prompt ») :Le script construit une requête spécifique (un « prompt ») pour Gemini. Ce prompt est crucial : il explique à l’IA son rôle (« Vous êtes un répondeur d’e-mails intelligent »), lui fournit le contexte de l’e-mail (expéditeur, sujet, corps du message) et lui demande de générer deux éléments distincts : un résumé concis de la requête (summary_text) et une réponse polie et utile (reply_text). J’ai spécifiquement formaté la demande pour que Gemini renvoie ces informations dans un objet JSON, ce qui facilite leur traitement par le script. Voici un exemple simplifié du type d’instruction donné à Gemini : "Vous êtes un répondeur d'e-mails intelligent... Veuillez fournir la réponse dans un objet JSON avec les clés 'summary_text' et 'reply_text'. Contenu de l'e-mail reçu : [Expéditeur, Sujet, Corps]"
  2. L’Appel API (UrlFetchApp.fetch) :Le script utilise le service UrlFetchApp d’Apps Script pour envoyer cette requête (le prompt) à l’API Gemini de Google. C’est une requête HTTP POST sécurisée, où votre clé API est utilisée pour authentifier la demande.
  3. La réponse de Gemini :Une fois la requête traitée, Gemini renvoie une réponse JSON. Le script analyse cette réponse pour extraire le summary_text et le reply_text générés par l’IA. Si tout se passe bien, ces deux éléments sont prêts à être utilisés. En cas d’erreur ou si le format n’est pas celui attendu, le script gère ces exceptions pour éviter les blocages.
  4. Envoi de la réponse formatée (envoyerReponseFormatee) :Enfin, la réponse générée par Gemini est intégrée dans un modèle HTML élégant. Ce modèle inclut un en-tête personnalisé pour l’expéditeur, le message de base de votre répondeur, le résumé de la requête et la réponse de l’IA, le tout dans un format clair et professionnel. Le script utilise ensuite messageOriginal.reply() pour envoyer cette réponse HTML.

Ce projet démontre la puissance de l’intégration des services Google (Gmail, Apps Script) avec les capacités avancées de l’intelligence artificielle de Gemini. C’est un pas de géant vers une gestion des e-mails plus intelligente et moins contraignante.

Le script pour les plus geeks d’entre vous

/**
 * Script Apps Script pour un répondeur automatique intelligent avec Gemini AI.
 *
 * Développeur: Fabrice FAUCHEUX
 * Date: 17 Juin 2025
 */

// --- Variables de Configuration ---
const CLE_API_GEMINI = 'VOTRE_CLE_GEMINI'; // Remplacez par votre clé API Gemini
const URL_API_GEMINI = "https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=" + CLE_API_GEMINI;
const NOM_LIBELLE_REPONSE = "Reponse intelligente"; // Nom du libellé pour les e-mails traités
const MESSAGE_BASE_REPONSE = "Merci pour votre message. Je vous répondrai dès que possible. Voici un bref résumé de votre requête et ma réponse automatisée initiée par l'IA Gemini:";
const LISTE_EXPEDITEURS_EXCLUS = [
  "accueil@entreprise.com",
  "support@entreprise.com,
  "support@autreentreprise.fr",
  // Ajoutez d'autres adresses à exclure ici (en minuscules)
];


/**
 * @overview Configure le déclencheur temporel pour exécuter 'gererRepondeurPrincipal' toutes les 5 minutes
 * et assure la création du libellé Gmail nécessaire.
 * @author Fabrice FAUCHEUX
 * @returns {void}
 */
function configurerRepondeurIntelligent() {
  // --- Création ou vérification du libellé ---
  const nomLibelle = NOM_LIBELLE_REPONSE;
  let libelle = GmailApp.getUserLabelByName(nomLibelle);
  if (!libelle) {
    try {
      libelle = GmailApp.createLabel(nomLibelle);
      Logger.log(`Libellé '${nomLibelle}' créé avec succès.`);
    } catch (erreur) {
      Logger.log(`Erreur lors de la création du libellé '${nomLibelle}': ${erreur.message}`);
      return; // Arrête l'exécution si le libellé ne peut pas être créé
    }
  } else {
    Logger.log(`Libellé '${nomLibelle}' existe déjà.`);
  }

  // --- Suppression des déclencheurs existants pour éviter les doublons ---
  const declencheursExistants = ScriptApp.getProjectTriggers();
  declencheursExistants.forEach(declencheur => {
    if (declencheur.getHandlerFunction() === "gererRepondeurPrincipal") {
      try {
        ScriptApp.deleteTrigger(declencheurs);
        Logger.log("Déclencheur 'gererRepondeurPrincipal' existant supprimé.");
      } catch (erreur) {
        Logger.log(`Erreur lors de la suppression d'un déclencheur existant : ${erreur.message}`);
      }
    }
  });

  // --- Création du nouveau déclencheur temporel ---
  try {
    ScriptApp.newTrigger("gererRepondeurPrincipal")
      .timeBased()
      .everyMinutes(5)
      .create();
    Logger.log("Configuration du Répondeur Intelligent terminée. Déclencheur défini pour 'gererRepondeurPrincipal' toutes les 5 minutes.");
  } catch (erreur) {
    Logger.log(`Erreur lors de la création du nouveau déclencheur : ${erreur.message}`);
  }
}

/**
 * @overview Fonction principale pour traiter les e-mails non lus :
 * récupère les e-mails, génère des réponses via l'API Gemini,
 * répond, marque comme lu et attribue un libellé.
 * @author Fabrice FAUCHEUX
 * @returns {void}
 */
function gererRepondeurPrincipal() {
  Logger.log("Démarrage de la fonction gererRepondeurPrincipal...");

  const libelleReponse = GmailApp.getUserLabelByName(NOM_LIBELLE_REPONSE);

  // --- Vérification de l'existence du libellé ---
  if (!libelleReponse) {
    Logger.log(`Libellé '${NOM_LIBELLE_REPONSE}' introuvable. Veuillez exécuter 'configurerRepondeurIntelligent()' d'abord.`);
    return;
  }

  // --- Recherche des conversations non lues et non traitées ---
  const requeteRecherche = `is:unread -label:"${NOM_LIBELLE_REPONSE}"`;
  let conversationsNonLues;
  try {
    conversationsNonLues = GmailApp.search(requeteRecherche);
  } catch (erreur) {
    Logger.log(`Erreur lors de la recherche d'e-mails : ${erreur.message}`);
    return;
  }

  if (conversationsNonLues.length === 0) {
    Logger.log("Aucun nouvel e-mail non lu à traiter.");
    return;
  }

  conversationsNonLues.forEach(conversation => {
    // --- Traitement de chaque conversation ---
    try {
      const messages = conversation.getMessages();
      const dernierMessage = messages[messages.length - 1]; // Le dernier message est le plus récent

      const expediteur = dernierMessage.getFrom();
      const sujet = dernierMessage.getSubject();
      const corpsMessage = dernierMessage.getPlainBody(); // Corps en texte brut pour l'API Gemini

      // --- Vérification si l'expéditeur est sur la liste d'exclusion ---
      // On extrait juste l'adresse e-mail si le format est "Nom <email@domaine.com>"
      const adresseExpediteur = expediteur.match(/<([^>]+)>/) ? expediteur.match(/<([^>]+)>/)[1] : expediteur;

      if (LISTE_EXPEDITEURS_EXCLUS.includes(adresseExpediteur.toLowerCase())) {
        Logger.log(`E-mail de '${expediteur}' ignoré car l'adresse '${adresseExpediteur}' est dans la liste d'exclusion.`);
        // Marque l'e-mail comme lu et labellise pour ne pas le retraiter
        dernierMessage.markRead();
        conversation.addLabel(libelleReponse);
        return; // Passe au message suivant
      }

      Logger.log(`Traitement de l'e-mail de : ${expediteur}`);
      Logger.log(`Sujet : ${sujet}`);

      const reponseGemini = genererReponseAvecGemini(expediteur, sujet, corpsMessage);

      if (reponseGemini && reponseGemini.replyText && reponseGemini.summaryText) {
        Logger.log(`Réponse générée : ${reponseGemini.replyText}`);
        Logger.log(`Résumé généré : ${reponseGemini.summaryText}`);

        // --- Envoi de la réponse formatée en HTML ---
        envoyerReponseFormatee(dernierMessage, reponseGemini.replyText, reponseGemini.summaryText);
        Logger.log(`Répondu à : ${expediteur}`);

        // --- Marquage comme lu et attribution du libellé ---
        dernierMessage.markRead(); // Marque le message spécifique comme lu
        conversation.addLabel(libelleReponse); // Ajoute le libellé à toute la conversation
        Logger.log(`Message marqué comme lu et conversation libellée '${NOM_LIBELLE_REPONSE}'.`);
      } else {
        Logger.log("Le contenu de la réponse ou du résumé est vide après l'appel à l'API Gemini.");
        // Optionnel : Marquer comme lu et labelliser même sans réponse pour éviter de le retraiter
        dernierMessage.markRead();
        conversation.addLabel(libelleReponse);
      }
    } catch (erreur) {
      Logger.log(`Erreur lors du traitement d'une conversation : ${erreur.message}`);
    }
  });

  Logger.log("Fonction gererRepondeurPrincipal terminée.");
}

/**
 * @overview Génère une réponse d'e-mail en utilisant l'API Gemini.
 * @author Fabrice FAUCHEUX
 * @param {string} expediteur - L'adresse e-mail de l'expéditeur original.
 * @param {string} sujet - Le sujet de l'e-mail original.
 * @param {string} corpsMessage - Le corps en texte brut de l'e-mail original.
 * @returns {Object|null} Un objet contenant 'replyText' et 'summaryText' ou null en cas d'erreur.
 */
function genererReponseAvecGemini(expediteur, sujet, corpsMessage) {
  // J'ai modifié le prompt pour demander explicitement les deux champs : summary_text et reply_text
  const invitePrompt = `Vous êtes un répondeur d'e-mails intelligent. Basé sur le contenu de l'e-mail suivant et le message de base du répondeur, générez un résumé concis de la requête de l'expéditeur et une réponse polie et utile dans la langue de l'expéditeur de l'e-mail original. Demander si possible de plus amples informations si la demande le justifie. La réponse doit ressembler à une réponse automatisée et intelligente. Veuillez fournir la réponse dans un objet JSON avec les clés "summary_text" et "reply_text".\n\nMessage de base du répondeur : "${MESSAGE_BASE_REPONSE}"\n\nContenu de l'e-mail reçu :\nDe : ${expediteur}\nSujet : ${sujet}\nCorps :\n${corpsMessage}\n\nExemple de JSON attendu: {"summary_text": "Votre résumé ici.", "reply_text": "Votre réponse ici."}`;

  const optionsRequete = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'x-goog-api-key': CLE_API_GEMINI
    },
    payload: JSON.stringify({
      contents: [{
        parts: [{
          text: invitePrompt
        }]
      }]
    }),
    muteHttpExceptions: true // Permet d'inspecter les réponses d'erreur
  };

  Logger.log("Envoi de la requête à l'API Gemini...");
  try {
    const reponseApi = UrlFetchApp.fetch(URL_API_GEMINI, optionsRequete);
    const codeReponse = reponseApi.getResponseCode();
    const corpsReponse = reponseApi.getContentText();

    if (codeReponse === 200) {
      const sortieGemini = JSON.parse(corpsReponse);
      let texteGenere = sortieGemini.candidates[0]?.content?.parts[0]?.text || '';

      try {
        const indexDebutJson = texteGenere.indexOf('{');
        const indexFinJson = texteGenere.lastIndexOf('}');
        if (indexDebutJson !== -1 && indexFinJson !== -1 && indexFinJson > indexDebutJson) {
          const chaineJson = texteGenere.substring(indexDebutJson, indexFinJson + 1);
          const reponseJson = JSON.parse(chaineJson);

          if (reponseJson.reply_text && reponseJson.summary_text) {
            return {
              replyText: reponseJson.reply_text,
              summaryText: reponseJson.summary_text
            };
          } else {
            Logger.log("La réponse JSON de l'API Gemini ne contient pas les clés 'reply_text' et/ou 'summary_text'. Texte complet: " + texteGenere);
            // Fallback: si l'API ne donne pas le bon format, on renvoie ce qu'on peut (peut nécessiter une gestion différente en amont)
            return {
              replyText: texteGenere,
              summaryText: "Résumé non disponible."
            };
          }
        } else {
          Logger.log("La réponse de l'API Gemini ne contient pas d'objet JSON valide. Texte complet: " + texteGenere);
          return {
            replyText: texteGenere,
            summaryText: "Résumé non disponible."
          };
        }
      } catch (erreurParseJson) {
        Logger.log(`Erreur lors du parsing JSON de la réponse Gemini : ${erreurParseJson.message}. Texte Gemini complet : ${texteGenere}`);
        return {
          replyText: texteGenere,
          summaryText: "Résumé non disponible."
        };
      }
    } else {
      Logger.log(`La requête API Gemini a échoué avec le code ${codeReponse} : ${corpsReponse}`);
      return null;
    }
  } catch (erreur) {
    Logger.log(`Erreur lors de l'appel à l'API Gemini : ${erreur.message}`);
    return null;
  }
}

/**
 * @overview Envoie une réponse à un message Gmail, formatée en HTML.
 * @author Fabrice FAUCHEUX
 * @param {GmailMessage} messageOriginal - Le message Gmail auquel répondre.
 * @param {string} contenuReponse - Le contenu textuel de la réponse générée par Gemini.
 * @param {string} sujetResume - Le résumé du sujet de l'e-mail, également généré par Gemini.
 * @returns {void}
 */
function envoyerReponseFormatee(messageOriginal, contenuReponse, sujetResume) {
  // Récupération du nom de l'expéditeur pour une personnalisation
  const expediteur = messageOriginal.getFrom();
  let bonjourExpediteur = "Bonjour,";
  const matchNom = expediteur.match(/^"?(.*?)"?\s*<.*>$/); // Extrait "Nom Prénom" ou "Nom"
  if (matchNom && matchNom[1]) {
    const nomExtrait = matchNom[1].trim();
    if (nomExtrait) {
      bonjourExpediteur = `Bonjour ${nomExtrait},`;
    }
  }

  // --- Style CSS pour la réponse HTML ---
  const styleCss = `
    body {
      font-family: Arial, sans-serif;
      font-size: 14px;
      color: #333;
      line-height: 1.6;
    }
    .conteneur-reponse {
      border: 1px solid #ddd;
      padding: 15px;
      margin-top: 20px;
      background-color: #f9f9f9;
      border-radius: 8px;
    }
    .entete-reponse {
      font-weight: bold;
      color: #0056b3;
      margin-bottom: 10px;
    }
    .resume-requete, .reponse-automatique {
      margin-bottom: 15px;
    }
    .resume-requete strong, .reponse-automatique strong {
      color: #0056b3;
    }
    ul {
      list-style-type: disc;
      margin-left: 20px;
      padding-left: 0;
    }
    li {
      margin-bottom: 5px;
    }
    .signature {
      margin-top: 20px;
      font-size: 12px;
      color: #777;
    }
  `;

  // --- Préparation du contenu de la réponse (gestion des retours à la ligne pour HTML) ---
  const contenuReponseHtml = contenuReponse.replace(/\n/g, '<br>');
  const sujetResumeHtml = sujetResume.replace(/\n/g, '<br>');


  // --- Structure HTML de la réponse ---
  const corpsHtml = `
    <html>
      <head>
        <style>
          ${styleCss}
        </style>
      </head>
      <body>
        <p>${bonjourExpediteur}</p>
        <p>${MESSAGE_BASE_REPONSE}</p>

        <div class="conteneur-reponse">
          <div class="resume-requete">
            <strong>Résumé de votre requête :</strong><br>
            ${sujetResumeHtml}
          </div>
          <div class="reponse-automatique">
            <strong>Réponse automatisée :</strong><br>
            ${contenuReponseHtml}
          </div>
        </div>

        <div class="signature">
          <p>Votre signature</p>
        </div>
      </body>
    </html>
  `;

  try {
    messageOriginal.reply("", {
      htmlBody: corpsHtml
    });
  } catch (erreur) {
    Logger.log(`Erreur lors de l'envoi de la réponse HTML : ${erreur.message}`);
  }
}

Avez-vous déjà imaginé d’autres façons d’utiliser l’IA pour automatiser vos tâches quotidiennes ? Partagez vos idées en commentaires !