📍 Article principal : Google Workspace pour PME : architecture et gouvernance
Ce tutoriel construit pas-à-pas une automatisation Apps Script de production : facturation depuis un Sheets, génération PDF, envoi par mail. Pour le contexte stratégique sur Apps Script et AppSheet, lire le guide de référence.
Introduction
Une PME passe en moyenne entre deux et six heures par semaine sur des tâches répétitives entre Sheets, Docs, Drive et Gmail. Saisir un devis dans un tableau, le mettre en forme dans un document, l’exporter en PDF, l’envoyer par mail au client, archiver une copie sur Drive, mettre à jour le statut dans le tableau — chaque étape prend une minute, mais multipliée par cent devis par mois cela représente une journée et demie de travail manuel répétitif. Apps Script automatise ce flux en moins de cent lignes de code, exécutées sur l’infrastructure Google sans serveur à maintenir. Ce tutoriel construit le script complet de facturation automatique en huit étapes.
Prérequis
- Compte Google Workspace Business Standard ou supérieur (les quotas Apps Script Workspace sont nettement plus généreux que sur compte gratuit).
- Connaissances JavaScript de base — savoir lire une boucle, une condition, une fonction.
- Un Google Sheet de devis existant ou prêt à créer.
- Un dossier Drive partagé pour archiver les PDF générés.
- Niveau attendu : intermédiaire. Vous avez déjà touché du JavaScript ou un autre langage de scripting.
- Temps estimé : 90 minutes de la création à la mise en production.
Étape 1 — Préparer la feuille de calcul source
Avant de toucher au code, on prépare la donnée. Apps Script lit et écrit dans des cellules par numéro de ligne et de colonne — si la structure de la feuille change après écriture du script, le script casse. La discipline qui paye consiste à figer un schéma de colonnes dès le départ et à ne plus le toucher.
Créez un nouveau Google Sheets dans votre Drive partagé Commercial. Nommez-le Devis 2026. Sur la première ligne, posez les en-têtes suivants dans cet ordre exact, de la colonne A à la colonne H : Date, N° devis, Client, Email client, Description, Montant HT, Statut, Lien PDF. Saisissez deux ou trois lignes de devis fictifs pour les tests. Le statut peut prendre les valeurs Brouillon, À envoyer, Envoyé, Accepté, Refusé.
Cette structure est le contrat entre la feuille et le script. Toute colonne ajoutée à droite ne casse rien. Toute insertion de colonne au milieu casse tout. Documentez ce point dans une cellule à droite (par exemple en J1 : « Schéma figé — ne pas insérer de colonne avant H »).
Étape 2 — Créer le projet Apps Script lié
Apps Script fonctionne en deux modes : projet autonome (standalone) ou projet lié à un document (bound). Pour un script qui automatise un Sheets précis, le mode lié est plus simple — le script vit avec la feuille, est sauvegardé avec elle, et hérite naturellement de ses permissions.
Dans le Sheets Devis 2026, ouvrez le menu Extensions → Apps Script. Une nouvelle fenêtre s’ouvre sur l’éditeur Apps Script. Renommez le projet Automation Devis en cliquant sur le titre par défaut.
Vérifiez immédiatement le runtime : cliquez sur l’icône d’engrenage Paramètres à gauche. Le champ Activer le runtime Chrome V8 doit être coché — c’est le runtime moderne. Si vous voyez à la place Activer le runtime Rhino, vous êtes sur un projet ancien dont l’exécution s’est arrêtée le 31 janvier 2026 ; vous devez basculer sur V8 avant d’écrire la moindre ligne.
Toujours dans les paramètres, cochez la case Afficher le fichier de manifeste appsscript.json. Cette option rend visible le fichier de configuration du projet, qu’on va utiliser à l’étape 6 pour déclarer les autorisations explicites.
Étape 3 — Écrire la fonction de génération PDF
La logique de facturation se décompose en trois fonctions : récupérer la ligne du devis à traiter, générer un PDF à partir d’un modèle Docs, envoyer le PDF par mail et archiver. On commence par la génération PDF parce que c’est la brique la plus visuelle — on saura tout de suite si elle marche.
Préparez d’abord un modèle Google Docs avec les balises de fusion. Créez un Docs Modele Devis dans le drive Commercial avec un contenu type :
DEVIS N° {{NUMERO}}
Date : {{DATE}}
Client : {{CLIENT}}
Description :
{{DESCRIPTION}}
Montant HT : {{MONTANT}} EUR
TVA 20 % : {{TVA}} EUR
Montant TTC : {{TTC}} EUR
Bon pour accord, le ... à ...
Notez l’identifiant de ce Docs (la chaîne entre /d/ et /edit dans l’URL) — vous en aurez besoin dans le script.
De retour dans l’éditeur Apps Script, remplacez le contenu du fichier Code.gs par la fonction suivante :
const TEMPLATE_DOC_ID = '1abcDEF...VOTRE_ID_DOCS';
const ARCHIVE_FOLDER_ID = '1xyzGHI...VOTRE_ID_DOSSIER';
function genererPdfDevis(donneesDevis) {
const modele = DriveApp.getFileById(TEMPLATE_DOC_ID);
const copie = modele.makeCopy(`Devis_${donneesDevis.numero}_${donneesDevis.client}`);
const doc = DocumentApp.openById(copie.getId());
const corps = doc.getBody();
const ttc = donneesDevis.montant * 1.20;
const tva = donneesDevis.montant * 0.20;
corps.replaceText('{{NUMERO}}', donneesDevis.numero);
corps.replaceText('{{DATE}}', donneesDevis.date);
corps.replaceText('{{CLIENT}}', donneesDevis.client);
corps.replaceText('{{DESCRIPTION}}', donneesDevis.description);
corps.replaceText('{{MONTANT}}', donneesDevis.montant.toFixed(2));
corps.replaceText('{{TVA}}', tva.toFixed(2));
corps.replaceText('{{TTC}}', ttc.toFixed(2));
doc.saveAndClose();
const pdfBlob = copie.getAs('application/pdf');
const dossier = DriveApp.getFolderById(ARCHIVE_FOLDER_ID);
const pdfFile = dossier.createFile(pdfBlob).setName(`Devis_${donneesDevis.numero}.pdf`);
copie.setTrashed(true);
return pdfFile;
}
Cette fonction reçoit un objet de données, copie le modèle Docs, remplace les balises de fusion par les valeurs réelles, exporte en PDF, archive le PDF dans le dossier dédié, et supprime la copie temporaire du Docs. Le retour est l’objet fichier PDF, qui contient l’URL exploitable à l’étape suivante. Notez que setTrashed(true) met à la corbeille la copie de travail — le PDF lui reste, c’est lui qu’on conserve.
Étape 4 — Tester la génération PDF
Avant d’écrire la suite, on teste cette fonction isolément. C’est le pattern de développement Apps Script qui fait gagner le plus de temps : on construit étape par étape, on teste chaque brique, on enchaîne. Tester l’enchaînement complet sans avoir validé chaque pièce mène à des heures de débogage.
Ajoutez en bas du fichier la fonction de test :
function testGenerationPdf() {
const donneesTest = {
numero: 'DEV-TEST-001',
date: '04/05/2026',
client: 'Acme SARL',
description: 'Audit de sécurité Workspace — 2 jours',
montant: 1500
};
const pdf = genererPdfDevis(donneesTest);
Logger.log('PDF généré : ' + pdf.getUrl());
}
Remplacez les constantes TEMPLATE_DOC_ID et ARCHIVE_FOLDER_ID par vos vrais identifiants. Sauvegardez le projet (icône disquette ou Ctrl+S). Dans la barre supérieure, sélectionnez la fonction testGenerationPdf dans le sélecteur, puis cliquez sur Exécuter.
Au premier lancement, Google demande votre autorisation explicite pour accéder à Drive et Docs. Cliquez Vérifier les autorisations, choisissez votre compte, validez. L’exécution démarre. Au bout de 3 à 5 secondes, le panneau d’exécution affiche le log avec l’URL du PDF généré. Cliquez sur cette URL — vous devez voir le PDF rempli avec les données de test, archivé dans votre dossier Drive cible. Si le PDF est vide ou les balises non remplacées, vérifiez que vos balises dans le modèle Docs sont strictement {{NUMERO}} et pas { {NUMERO} } avec espaces parasites.
Étape 5 — Ajouter l’envoi par mail
Le PDF est généré et archivé. Reste à l’envoyer au client par mail. Apps Script expose GmailApp pour envoyer depuis le compte de l’utilisateur exécutant. Le quota Workspace est de 1 500 destinataires par jour, ce qui couvre largement les besoins de facturation d’une PME.
Ajoutez la fonction d’envoi :
function envoyerDevisParMail(donneesDevis, pdfFile) {
const objet = `Votre devis ${donneesDevis.numero}`;
const corps = `Bonjour ${donneesDevis.client},
Veuillez trouver ci-joint le devis ${donneesDevis.numero} d'un montant de ${donneesDevis.montant.toFixed(2)} EUR HT.
Nous restons à votre disposition pour tout complément d'information.
Cordialement,
L'équipe commerciale`;
GmailApp.sendEmail(donneesDevis.email, objet, corps, {
attachments: [pdfFile.getBlob()],
name: 'Service Commercial'
});
}
L’option name définit le nom d’expéditeur affiché côté client — c’est plus professionnel que d’apparaître avec le seul prénom de l’employé qui exécute le script. Le corps du mail est en texte brut ici pour la simplicité ; pour un mail formaté HTML, on remplace corps par htmlBody dans les options.
Testez en ajoutant email: 'votre.email.test@gmail.com' dans donneesTest, puis enchaînez dans testGenerationPdf un appel envoyerDevisParMail(donneesTest, pdf);. Exécutez. Vous devez recevoir le mail avec le PDF en pièce jointe en moins d’une minute. Si rien n’arrive, vérifiez le dossier spam — sur un domaine fraîchement configuré sans DKIM, c’est fréquent.
Étape 6 — Brancher le menu personnalisé sur le Sheets
Le script fonctionne mais il faut aller dans l’éditeur pour le déclencher. C’est inutilisable pour un commercial. La solution standard consiste à exposer la fonction dans un menu personnalisé du Sheets — un menu Devis apparaît à côté de Aide, et un clic suffit pour traiter la ligne sélectionnée.
Ajoutez la fonction d’amorçage et la fonction de traitement de ligne :
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Devis')
.addItem('Envoyer le devis sélectionné', 'envoyerDevisLigneActive')
.addToUi();
}
function envoyerDevisLigneActive() {
const feuille = SpreadsheetApp.getActiveSheet();
const ligne = feuille.getActiveRange().getRow();
if (ligne === 1) {
SpreadsheetApp.getUi().alert('Sélectionnez une ligne de devis, pas l’en-tête.');
return;
}
const valeurs = feuille.getRange(ligne, 1, 1, 8).getValues()[0];
const donnees = {
date: Utilities.formatDate(valeurs[0], Session.getScriptTimeZone(), 'dd/MM/yyyy'),
numero: valeurs[1],
client: valeurs[2],
email: valeurs[3],
description: valeurs[4],
montant: parseFloat(valeurs[5])
};
const pdf = genererPdfDevis(donnees);
envoyerDevisParMail(donnees, pdf);
feuille.getRange(ligne, 7).setValue('Envoyé');
feuille.getRange(ligne, 8).setValue(pdf.getUrl());
SpreadsheetApp.getUi().alert(`Devis ${donnees.numero} envoyé à ${donnees.email}`);
}
La fonction onOpen est un déclencheur simple — elle s’exécute automatiquement à chaque ouverture du Sheets et installe le menu. La fonction de traitement lit la ligne active, prépare les données, déclenche la chaîne PDF + mail, met à jour le statut et l’URL du PDF dans les colonnes G et H. Sauvegardez, fermez et rouvrez le Sheets : le menu Devis apparaît à droite du menu Aide.
Sélectionnez une ligne de test, cliquez Devis → Envoyer le devis sélectionné. Le script s’exécute, le PDF est généré, le mail part, les colonnes G et H sont mises à jour. Vous venez de transformer trois minutes de travail manuel en deux clics.
Étape 7 — Déclarer les scopes dans le manifeste
Apps Script fonctionne avec un consentement progressif — au premier usage, l’utilisateur est invité à autoriser les scopes nécessaires. Pour un script qui sera partagé entre plusieurs commerciaux, on déclare explicitement les scopes dans le manifeste pour rendre l’autorisation prévisible.
Ouvrez appsscript.json dans l’éditeur (visible si vous avez coché l’option à l’étape 2). Remplacez son contenu par :
{
"timeZone": "Africa/Dakar",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.currentonly",
"https://www.googleapis.com/auth/documents",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.send_mail",
"https://www.googleapis.com/auth/script.container.ui"
]
}
Adaptez le timezone à votre zone réelle. Les cinq scopes sont le minimum nécessaire : Sheets en cours, Docs, Drive, envoi de mail, UI conteneur. Sauvegardez. À la prochaine exécution par un autre commercial, l’écran d’autorisation listera précisément ces cinq scopes.
Étape 8 — Mise en production et trigger journalier
L’envoi à la demande couvre le cas standard. Pour les rappels de devis non répondus, on ajoute un trigger journalier qui parcourt les lignes au statut Envoyé de plus de 7 jours et envoie un rappel automatique.
Ajoutez la fonction de balayage :
function rappelDevisNonRepondus() {
const feuille = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1') || SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
const donnees = feuille.getDataRange().getValues();
const seuil = new Date();
seuil.setDate(seuil.getDate() - 7);
for (let i = 1; i < donnees.length; i++) {
const dateDevis = donnees[i][0];
const statut = donnees[i][6];
const email = donnees[i][3];
if (statut === 'Envoyé' && dateDevis instanceof Date && dateDevis < seuil && email) {
GmailApp.sendEmail(email, `Rappel devis ${donnees[i][1]}`,
`Bonjour, nous revenons vers vous concernant le devis ${donnees[i][1]} envoyé le ${Utilities.formatDate(dateDevis, Session.getScriptTimeZone(), 'dd/MM/yyyy')}. Restez-vous intéressé ?`);
feuille.getRange(i + 1, 7).setValue('Relancé');
}
}
}
Pour planifier l’exécution quotidienne, allez dans l’éditeur Apps Script, cliquez sur l’icône horloge Déclencheurs. Ajoutez un déclencheur sur rappelDevisNonRepondus, type Déclenchement temporel, fréquence Quotidien, plage horaire 9h-10h. Sauvegardez. Chaque matin, le script balaie le Sheets et relance les devis stagnants.
Surveillez les premières journées d’exécution via Exécutions dans l’éditeur. Chaque exécution est plafonnée à 6 minutes — pour un Sheets de moins de 500 devis, on est très en-dessous. Si vous montez à 5 000 devis, il faudra découper en lots avec PropertiesService pour persister la position et reprendre l’exécution suivante.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| Exécution timeout 6 minutes | Trop de lignes traitées en un appel | Découper avec PropertiesService et trigger récurrent |
| Authorization required | Premier appel d’un nouveau scope | Exécuter manuellement la fonction concernée pour autoriser |
| PDF vide | Balises mal écrites dans le modèle | Vérifier {{NUMERO}} sans espaces, casse exacte |
| Mails partent en spam | SPF/DKIM non configurés | Voir tutoriel de setup initial, étape 8 |
| Quota MailApp dépassé | Plus de 1 500 destinataires/24h | Workspace : attendre 24h, ou utiliser Gmail API avec quota supérieur |
| Runtime Rhino actif | Projet ancien jamais migré | Bascule V8 immédiate dans Paramètres du projet |
Adaptation aux conditions locales
Pour les structures qui facturent en CFA, adaptez la TVA selon le pays (18 % au Sénégal, 18 % en Côte d’Ivoire, 18 % au Mali, 19,25 % au Cameroun, 16 % en Guinée). Modifiez la constante dans la fonction genererPdfDevis. Pour les structures multi-pays, déportez la TVA dans une feuille de paramètres dédiée et chargez-la dynamiquement.
Pour les paiements via mobile money, ajoutez une colonne supplémentaire Lien de paiement dans la feuille et insérez dans le mail un lien Wave ou Mixx by Yas qui pré-rempliit le destinataire et le montant. Le commercial copie un lien de paiement, le client scanne le QR, le règlement arrive sur le compte marchand de l’entreprise.
Tutoriels associés
- Construire une application AppSheet sans code — quand le besoin dépasse le script et entre dans l’application.
- Mettre en place les Drive partagés Google Workspace — le dossier d’archivage des PDF doit vivre dans un drive partagé.
Pour aller plus loin
- 🔝 Retour au guide de référence : Google Workspace pour PME : architecture et gouvernance
- Quotas Apps Script — documentation officielle
- Runtime V8 Apps Script
- DocumentApp — référence API
- GmailApp — référence API
- Déclencheurs installables
FAQ
Combien coûte Apps Script ?
Apps Script est inclus dans tous les plans Google Workspace sans coût additionnel. Les seules limites sont les quotas d’exécution : 6 minutes par exécution, 100 000 appels URL Fetch par jour, 1 500 destinataires email par jour pour un compte Workspace.
Le script peut-il s’exécuter sans utilisateur connecté ?
Oui via les déclencheurs temporels — le script tourne en tâche de fond aux heures programmées. Pour un déclenchement manuel par utilisateur, c’est l’utilisateur qui exécute, et donc qui consomme son quota personnel.
Que faire si on dépasse les 6 minutes ?
On découpe en lots persistés. Le pattern : enregistrer la position courante dans PropertiesService, lever volontairement avant 5 minutes 30, programmer un trigger qui reprendra à la position sauvegardée. Apps Script propose une approche standard avec ScriptApp.newTrigger().
Peut-on tester sans envoyer de vrais mails ?
Oui, remplacez temporairement GmailApp.sendEmail par GmailApp.createDraft qui crée un brouillon dans la boîte de l’utilisateur sans l’envoyer. Vérifiez visuellement, puis basculez sur sendEmail en production.
Comment versionner le code Apps Script ?
L’éditeur intègre un historique des versions. Pour un versionnement plus sérieux, utilisez l’outil clasp en ligne de commande qui synchronise un projet Apps Script avec un dépôt Git local. Cela permet de travailler en équipe avec revues de code.
Le runtime Rhino est-il vraiment hors ligne en 2026 ?
Oui. Rhino est déprécié depuis février 2025 et l’exécution s’est arrêtée le 31 janvier 2026. Tout projet ancien doit être migré vers V8 — l’opération consiste à activer V8 dans les paramètres et à corriger les rares incompatibilités syntaxiques (souvent for each qui devient for...of).