Tutoriel · Notifications

Notification Telegram ou email depuis un script : sauvegarde, cron, déploiement et Claude Code

Une sauvegarde qui échoue à 3 h du matin, un déploiement qui casse, une longue tâche confiée à Claude Code qui vient de se terminer : dans tous ces cas, tu voudrais un message sur ton téléphone. Voici comment le faire à la main avec un bot Telegram et curl, pourquoi ça devient vite pénible quand les scripts se multiplient, et comment envoyer toutes ces notifications vers une seule URL qui les répartit sur tes canaux.

Chaque script bricole ses propres alertes

Sur un serveur ou un poste de développement, on finit toujours par avoir une collection de scripts qui tournent sans surveillance : la sauvegarde de la base de données, le renouvellement d'un certificat, une synchronisation de fichiers, un déploiement, un import de données. Tant qu'ils réussissent, personne n'y pense. Le jour où l'un d'eux échoue, l'erreur part dans un fichier de log que personne ne lit.

Alors on ajoute une alerte. Dans un script, un envoi d'email avec la commande mail, qui marche tant que le serveur sait envoyer des emails et que ceux-ci n'arrivent pas en spam. Dans un autre, un bot Telegram, plus rapide à mettre en place. Dans un troisième, un webhook Slack copié depuis la documentation. Au bout de quelques mois, la situation ressemble souvent à ça :

  • Des jetons dispersés : le jeton du bot Telegram est collé en clair dans cinq scripts, sur trois machines.
  • Aucun historique : impossible de savoir si la sauvegarde de mardi a réussi, sauf à fouiller la conversation Telegram.
  • Aucun réglage central : changer de destinataire, ou envoyer aussi les erreurs par email, oblige à modifier chaque script.
  • Aucune protection contre les boucles : un script relancé en boucle envoie cent messages identiques en dix minutes.

Avant de voir comment centraliser tout ça, voici la méthode à la main, qui reste une très bonne solution pour un ou deux scripts.

La méthode à la main : un bot Telegram et curl

Telegram permet à n'importe qui de créer un bot gratuitement et de lui faire envoyer des messages avec une simple requête HTTP. C'est la méthode la plus répandue pour recevoir une notification sur son téléphone depuis un script.

1. Créer le bot

Dans Telegram, ouvre une conversation avec @BotFather, le bot officiel de création de bots, et envoie la commande /newbot. Il demande un nom, puis un identifiant qui doit se terminer par bot. À la fin, il te donne un jeton de la forme 123456789:AAH…. Ce jeton donne le contrôle complet du bot : garde-le secret.

2. Trouver l'identifiant de la conversation

Un bot ne peut pas écrire le premier à quelqu'un. Ouvre la conversation avec ton nouveau bot, envoie-lui un message quelconque, puis récupère l'identifiant de cette conversation :

curl -s "https://api.telegram.org/botVOTRE_JETON_BOT/getUpdates"

Dans la réponse, repère "chat":{"id":123456789 : ce nombre est le chat_id. Pour un groupe, ajoute le bot au groupe puis envoie /start dans le groupe : l'identifiant est alors négatif.

3. Envoyer un message depuis un script

curl -fsS -X POST "https://api.telegram.org/botVOTRE_JETON_BOT/sendMessage" \
  -d chat_id=VOTRE_CHAT_ID \
  --data-urlencode "text=Sauvegarde en échec sur srv-web-01" > /dev/null

Et dans un script de sauvegarde :

#!/usr/bin/env bash
TG_TOKEN="VOTRE_JETON_BOT"
TG_CHAT="VOTRE_CHAT_ID"

if ! /usr/local/bin/backup.sh; then
  curl -fsS -X POST "https://api.telegram.org/bot$TG_TOKEN/sendMessage" \
    -d chat_id="$TG_CHAT" \
    --data-urlencode "text=Sauvegarde en échec sur $(hostname)" > /dev/null
fi

C'est gratuit, rapide, et ça marche. Slack et Discord fonctionnent sur le même principe avec leurs « webhooks entrants » : une URL secrète à laquelle on envoie un petit JSON ({"text": "…"} pour Slack, {"content": "…"} pour Discord).

Les limites de la méthode à la main

Pour un script, rien à redire. Les difficultés arrivent avec le nombre de scripts, de machines et de personnes à prévenir :

  • Un jeton par script, en clair. Le jeton du bot est copié partout. S'il fuit (dans un dépôt Git, dans une capture d'écran), il faut le changer dans tous les scripts, sur toutes les machines.
  • Un canal par script. Le script qui écrit sur Telegram ne sait pas envoyer d'email. Prévenir aussi par email en cas d'erreur, c'est un deuxième envoi à écrire, avec un serveur d'envoi d'emails à configurer.
  • Pas de tri par importance. Les succès et les échecs arrivent au même endroit. Soit tu reçois trop de messages et tu finis par ne plus les lire, soit tu n'envoies que les échecs et tu ne sais jamais si la tâche a vraiment tourné.
  • Rien n'est rejoué. Si l'API de Telegram ne répond pas au moment de l'envoi, ou si le réseau du serveur est coupé quelques secondes, le message est perdu. Personne ne réessaie.
  • Pas d'historique. Le seul journal, c'est la conversation Telegram, mélangée avec le reste.
  • Pas de garde-fou. Un script qui part en boucle envoie autant de messages que de tours de boucle.

On peut régler chacun de ces points avec un petit script partagé, un fichier de configuration commun et une file d'attente. C'est exactement ce qu'un service de notification fait à ta place.

Avec Pinguro : une URL par flux, tous tes canaux

Les notifications par API de Pinguro reprennent l'idée du bot Telegram (une URL, un curl), mais l'URL ne pointe pas vers un canal précis : elle pointe vers un flux, et c'est le flux qui décide où part le message. Les canaux sont ceux déjà configurés pour les alertes de tes monitors : email, Slack, Discord, Telegram ou webhook, selon ton plan.

1. Crée un flux

Dans le tableau de bord, ouvre Notifications dans la barre latérale, puis + Nouveau flux. Donne-lui un nom qui dit d'où viennent les messages, par exemple « Sauvegarde du serveur ».

2. Choisis les canaux, et ce que chacun reçoit

Coche les canaux qui doivent recevoir les messages de ce flux. Pour chacun, choisis ce qu'il reçoit :

  • Tout ;
  • Succès, attention et erreurs ;
  • Attention et erreurs ;
  • Erreurs seulement.

Chaque message envoyé par un script a un niveau : info, success, warning ou error, du moins au plus important. Tu peux par exemple tout recevoir sur Telegram, et seulement les erreurs par email. Chaque canal coché reçoit son propre exemplaire.

3. Copie l'URL du flux

À la création, Pinguro affiche l'URL du flux, de la forme https://pinguro.app/n/nfy_…. Elle n'est affichée qu'une seule fois : copie-la tout de suite dans un endroit sûr. Si tu la perds, ou si elle fuit, le bouton Régénérer l'URL en crée une nouvelle et désactive immédiatement l'ancienne. Le bouton Envoyer un test envoie une notification d'essai sur les canaux du flux, pour vérifier que tout arrive (10 tests par heure et par flux, comptés dans le quota du jour).

4. Appelle l'URL

curl -fsS -X POST "https://pinguro.app/n/VOTRE_JETON" \
  --data-urlencode "title=Déploiement terminé" \
  --data-urlencode "message=Nouvelle version en production" \
  -d level=success

Trois champs : title, obligatoire (200 caractères au plus) ; message, facultatif (4 000 caractères au plus, retours à la ligne conservés) ; level, facultatif, info par défaut. Le texte est affiché tel quel : ni HTML ni Markdown ne sont interprétés. L'URL accepte aussi du JSON :

curl -fsS -X POST "https://pinguro.app/n/VOTRE_JETON" \
  -H "Content-Type: application/json" \
  -d '{"title":"Déploiement en échec","message":"Étape build : code 2","level":"error"}'

Une notification acceptée renvoie le code 202. Un champ invalide renvoie 400 avec le nom du champ en cause, une URL inconnue ou un flux désactivé renvoie 404, et un quota dépassé renvoie 429 avec un en-tête Retry-After. Pas de clé API à gérer : l'URL du flux suffit.

Telegram, Slack, Discord et webhook font partie des plans payants, qui ne sont pas encore ouverts. Aujourd'hui, le plan gratuit envoie les notifications par email. La méthode à la main décrite plus haut reste donc la solution gratuite pour Telegram, et rien n'empêche de combiner les deux.

Exemples : sauvegarde, cron, déploiement

Script de sauvegarde : succès ou échec

Le script envoie success quand tout va bien et error avec le code de sortie sinon. Côté flux, il suffit de régler l'email sur « Erreurs seulement » pour n'être dérangé qu'en cas de problème, tout en gardant la trace des succès dans l'historique.

#!/usr/bin/env bash
# Prévient sur tes canaux d'alerte selon le résultat de la sauvegarde.
NOTIFY_URL="https://pinguro.app/n/VOTRE_JETON"

if /usr/local/bin/backup.sh; then
  curl -fsS -X POST "$NOTIFY_URL" --data-urlencode "title=Sauvegarde terminée" -d level=success > /dev/null
else
  code=$?
  curl -fsS -X POST "$NOTIFY_URL" \
    --data-urlencode "title=Sauvegarde en échec" \
    --data-urlencode "message=Code de sortie : $code" \
    -d level=error > /dev/null
fi

Pour joindre la fin du journal au message d'erreur, ajoute par exemple --data-urlencode "message=$(tail -c 3000 /var/log/backup.log)" : les 3 000 derniers caractères restent sous la limite de 4 000, au-delà de laquelle la notification serait refusée.

Cron : seulement en cas d'échec

Dans une crontab, pas besoin de script : l'opérateur || n'exécute le curl que si la commande précédente a échoué.

# Tous les jours à 3 h : sauvegarde, et notification seulement en cas d'échec
0 3 * * * /usr/local/bin/backup.sh || curl -fsS -X POST "https://pinguro.app/n/VOTRE_JETON" --data-urlencode "title=Sauvegarde en échec" -d level=error > /dev/null

Déploiement avec GitHub Actions

Enregistre l'URL du flux comme secret du dépôt (Settings → Secrets and variables → Actions), sous le nom PINGURO_NOTIFY_URL, puis ajoute une étape en fin de workflow. Avec if: failure(), elle ne s'exécute que si une étape précédente a échoué :

      - name: Prévenir en cas d'échec
        if: failure()
        env:
          NOTIFY_URL: ${{ secrets.PINGURO_NOTIFY_URL }}
        run: |
          curl -fsS -X POST "$NOTIFY_URL" \
            --data-urlencode "title=Déploiement en échec" \
            --data-urlencode "message=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
            -d level=error > /dev/null

Le lien vers l'exécution, écrit en clair dans le message, reste en général cliquable dans la messagerie qui le reçoit.

Mise en pratique

Ton premier flux en deux minutes

Les notifications par API sont incluses dans le plan gratuit : 1 flux, 30 notifications par jour, envoyées par email. Sans carte bancaire.

Créer mon compte gratuit

Claude Code : être prévenu quand Claude a fini

Quand tu confies une longue tâche à Claude Code, l'assistant de code en ligne de commande d'Anthropic, tu n'as pas forcément envie de surveiller le terminal. Claude Code sait lancer une commande à certains moments, grâce aux hooks. Le hook Stop se déclenche chaque fois que Claude a fini de répondre : il suffit de lui faire appeler l'URL du flux.

Ajoute ce bloc dans ~/.claude/settings.json (s'il contient déjà un bloc "hooks", fusionne les deux) :

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "curl -fsS -X POST 'https://pinguro.app/n/VOTRE_JETON' --data-urlencode 'title=Claude a terminé' -d level=success > /dev/null || true"
          }
        ]
      }
    ]
  }
}

Trois détails comptent :

  • Le || true garantit qu'un échec réseau ne gêne jamais Claude Code : quoi qu'il arrive, la commande se termine sans erreur.
  • L'URL est un secret. Le fichier ~/.claude/settings.json est propre à ton compte sur la machine, il n'est en principe dans aucun dépôt. Pour un hook limité à un projet, préfère .claude/settings.local.json, le fichier de réglages personnels du projet, plutôt que .claude/settings.json, souvent partagé : ne commite jamais l'URL dans un dépôt partagé, car toute personne qui la lit peut envoyer des notifications sur ton flux.
  • Les messages identiques sont regroupés. Le hook envoie le même titre à chaque réponse. Si Claude termine plusieurs fois en moins de 10 minutes, seul le premier message est envoyé, les suivants sont comptés dans l'historique (« ×N »). Chaque message regroupé compte quand même dans le quota du jour : sur le plan gratuit, 30 notifications par jour, une session très active peut l'épuiser.

Bonnes pratiques

Traite l'URL comme un mot de passe

Toute personne qui connaît l'URL d'un flux peut envoyer des notifications sur tes canaux. Ne la commite pas dans un dépôt, ne la colle pas dans un ticket ou une capture d'écran. En cas de doute, régénère-la : l'ancienne cesse de fonctionner immédiatement.

Sur un serveur partagé, un détail compte : les arguments d'une commande sont visibles par les autres utilisateurs de la machine, par exemple avec ps, le temps qu'elle s'exécute. Pour que l'URL n'apparaisse pas dans la ligne de commande de curl, garde-la dans un fichier lisible par toi seul et passe-la à curl par l'option -K -, qui lit sa configuration sur l'entrée standard :

# Une seule fois : colle l'URL quand read l'attend (elle ne reste pas dans l'historique du shell)
mkdir -p ~/.config/pinguro
read -r NOTIFY_URL
( umask 077 && printf '%s\n' "$NOTIFY_URL" > ~/.config/pinguro/notify-url )

# Dans tes scripts
NOTIFY_URL="$(cat ~/.config/pinguro/notify-url)"

notify() {  # usage : notify <niveau> <titre> [message]
  printf 'url = "%s"\n' "$NOTIFY_URL" | curl -fsS -X POST -K - \
    --data-urlencode "title=$2" \
    --data-urlencode "message=${3:-}" \
    -d "level=$1" > /dev/null || true
}

notify error "Sauvegarde en échec" "Disque plein sur /var/backups"

printf est une commande intégrée au shell : l'URL ne passe par aucune ligne de commande visible. Le || true évite qu'une notification qui échoue fasse échouer le script lui-même.

Compte sur l'anti-boucle, mais ne t'y repose pas

Un message de même titre (majuscules et espaces en trop ignorés) et de même niveau, reçu sur le même flux moins de 10 minutes après le premier message identique, n'est pas renvoyé : l'historique affiche « ×N ». La fenêtre ne se prolonge pas à chaque répétition. En plus, un flux accepte au plus 10 nouvelles notifications par minute. Un script en boucle ne te noiera donc pas sous les messages, mais chaque message accepté, même regroupé, compte dans le quota du jour : un titre stable et un envoi par exécution restent la bonne habitude.

Laisse Pinguro réessayer

Quand l'URL répond 202, la notification est déjà enregistrée ; elle part aussitôt. Si un canal ne répond pas, Pinguro réessaie automatiquement au bout de 1, 5, 15, 30 et 60 minutes, puis toutes les heures, pendant 6 heures au plus après la réception. Ton script n'a donc rien à rejouer : il lui suffit que Pinguro ait accepté le message. L'historique de la page Notifications montre l'état de chaque envoi (Envoyé, Nouvel essai prévu, Abandonné).

Combine notification et heartbeat

Une notification transmet ce que ton script a à dire : réussite, échec, détail. Mais si le script ne tourne plus du tout (crontab effacée, serveur arrêté, machine qui ne redémarre pas), il n'envoie rien, et l'absence de message passe inaperçue. C'est le rôle d'un heartbeat : le script appelle une autre URL à chaque exécution réussie, et Pinguro t'alerte quand cet appel n'arrive plus dans le délai prévu.

if /usr/local/bin/backup.sh; then
  curl -fsS "https://pinguro.app/h/TON_TOKEN" > /dev/null   # signe de vie (heartbeat)
else
  code=$?
  notify error "Sauvegarde en échec" "Code de sortie : $code"
fi

L'URL du heartbeat est aussi un secret : sur une machine partagée, passe-la elle aussi par -K -, comme ci-dessus. Les deux se complètent : la notification t'explique l'échec tout de suite, le heartbeat te prévient du silence. Pour mettre en place le heartbeat, voir le guide Monitorer un cron job : être alerté quand une tâche planifiée plante.

Et pour être alerté quand un site ou une API tombe, sans être noyé de messages, voir Alerte Slack, Discord ou Telegram quand ton site tombe, sans bruit inutile.

Ce que permet chaque plan

Les notifications par API sont disponibles sur tous les plans, y compris le plan gratuit. Les limites :

Plan Flux actifs par compte Notifications par jour et par workspace Canaux
Free 1 30 Email
Solo 5 300 Email, Slack, Discord, Telegram, webhook
Agency 30 3 000 Email, Slack, Discord, Telegram, webhook
Enterprise 100 10 000 Email, Slack, Discord, Telegram, webhook

Le compteur du jour repart à zéro chaque jour à minuit (UTC). Les plans payants ne sont pas encore ouverts : aujourd'hui, seul le plan gratuit est disponible. Sur ce plan, les notifications partent par email, vers l'adresse de ton compte (un canal email est créé automatiquement à l'inscription) ou vers une autre adresse, que son destinataire doit confirmer avant de recevoir quoi que ce soit. Le détail complet (réponses, limites, format du webhook) est dans la documentation des notifications par API.

Questions fréquentes

Faut-il une clé API pour envoyer une notification ?

Non. Chaque flux a sa propre URL secrète, et c'est elle qui sert d'authentification. Un script n'a besoin de rien d'autre que curl (ou n'importe quel outil capable d'envoyer une requête POST).

Peut-on envoyer une notification depuis Python ou Node ?

Oui. L'URL accepte un formulaire classique ou du JSON. En Python : requests.post(url, data={"title": "Import terminé", "level": "success"}, timeout=10). En Node : await fetch(url, { method: "POST", body: new URLSearchParams({ title: "Import terminé", level: "success" }) }).

Que se passe-t-il si mon script envoie la même erreur toutes les minutes ?

Les messages identiques (même titre, même niveau, même flux) reçus moins de 10 minutes après le premier sont regroupés : tu reçois un seul message, et l'historique indique combien de fois il a été répété. Ils comptent tout de même dans le quota du jour.

Une notification peut-elle se perdre ?

Une fois acceptée (réponse 202), la notification est enregistrée, et Pinguro réessaie pendant 6 heures si un canal ne répond pas. Un canal supprimé ou en pause, ou une adresse email pas encore confirmée par son destinataire, ne reçoit rien. Côté script, si l'URL elle-même est injoignable, le curl échoue : c'est là qu'un heartbeat prend le relais.

Combien de temps l'historique est-il conservé ?

Le contenu des notifications est conservé 30 jours, puis supprimé automatiquement. L'historique et la liste des flux sont exportables en CSV depuis la page Notifications.

Conclusion

Pour un script isolé, un bot Telegram et une ligne de curl suffisent. Dès que les scripts se multiplient, le vrai besoin n'est plus d'envoyer un message, mais de savoir où il part, de trier les succès et les erreurs, de garder une trace et de ne rien perdre quand un canal ne répond pas.

Une URL par flux, des canaux choisis une fois pour toutes, un niveau par message : tes scripts de sauvegarde, tes crons, tes déploiements et Claude Code peuvent tous te prévenir de la même façon. Ajoute un heartbeat sur les tâches importantes, et tu ne découvriras plus une tâche arrêtée des jours plus tard.

À toi de jouer

Envoie ta première notification

Plan gratuit avec 1 flux de notifications, 30 envois par jour par email, 3 monitors et une page de statut publique. Pas de carte bancaire, pas d'engagement.

Créer mon compte gratuit
Publié le 4 octobre 2026. Une question ? [email protected]