Repository navigation
FR Bridge Connecteur Email
English · Type : procédure + référence · Public : administrateur · Contexte : Reality Bridge · Statut : prête · S'applique depuis : 4.2.5
Les connecteurs (email, Telnet, SSH, Open Badges) · contrat : expérimental · vérifié par la CI (matrice de support de la 4.4.0, 2026-10-23) : Node 22 sous Ubuntu (Telnet et SSH pas sous Windows) · vérifications automatiques : jobs
connectors,connector-archive-e2e; plan de CIreality; 3 fichiers de tests · passes humaines :connectors-real-securitypas faite (#79) · limites connues : aucune listée sur cette page
Le connecteur e-mail transforme une lettre en signal : un joueur répond au jeu par e-mail, et les mots de sa réponse proposent le signal que le jeu leur associe. Il lit les messages depuis le webhook signé d’un fournisseur ou depuis une boîte qu’il relève en IMAP, et n’envoie aucune réponse. Il est expérimental, comme tous les connecteurs (Vue d’ensemble des connecteurs).
Un processus quest-n-click-connector email qui lie un joueur par un code d’appairage envoyé en sujet d’un e-mail,
puis propose letter.door (ou le signal que le jeu déclare) pour chaque message dont les mots correspondent à une
réponse, une fois par message : un message livré deux fois fait un seul signal.
- Un connecteur à côté d’un Bridge en marche, depuis la 4.1.9 ; cette page s’applique à partir de la 4.2.5 (le nom
quest-n-click-connector). -
Vérifié sur : le mode webhook, comme dans les étapes ci-dessous, a été lancé le 23 octobre 2026 contre un Bridge
local du jeu
signals(macOS, Node 22.14, moteur 4.4.0) : une lettre d’appairage202 {"status":"paired"}, une réponse202 {"status":"accepted"}, la même réponse de nouveau200 {"status":"duplicate"}. Le mode IMAP n’a pas été rejoué pour cette page ; la CI joue les deux modes depuis l’archive de la release (npm run connector-archive-e2e), contre un petit serveur IMAP à elle, jamais un vrai fournisseur.
-
Un Bridge en marche (Installer et lancer le Bridge), et un jeu dont
reality.connectors.emaildéclare ses réponses (Signaux et manifeste) :connectors: { email: { answers: [{ words: ['open', 'door'], signal: 'letter.door' }], otherwise: 'letter.unclear' } }
-
Pour le mode webhook : un fournisseur (ou un petit adaptateur devant lui) capable de poster le message brut, signé comme plus bas. Pour le mode IMAP : une boîte joignable en TLS, et son mot de passe dans un fichier.
grant là où est le dossier du Bridge ; le connecteur sur la machine qui reçoit le webhook ou atteint la boîte, dans
un dossier qui contient sa configuration, son jeton et son secret (mode 600). npm run connector -- email … dans le
dépôt du moteur, npx quest-n-click-connector email … avec le paquet quest-n-click-connectors.
-
Donnez au connecteur son jeton, pour les signaux e-mail seulement, avec l’appairage :
# dans le dépôt du moteur (le dossier du Bridge est .cache/bridge) npm run bridge -- grant --connector=gate-mail --source=email --signals=letter.door,letter.unclear --pair > email.token
-
Écrivez le secret partagé du webhook dans un fichier (ou, pour IMAP, le mot de passe de la boîte) :
# dans le dossier du connecteur node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("hex"))' > webhook.secret chmod 600 webhook.secret
-
Écrivez sa configuration,
email.json. Le mode webhook :{ "bridge": { "url": "http://127.0.0.1:8787/", "tokenFile": "email.token" }, "gameId": "signals", "health": { "port": 9302 }, "email": { "mode": "webhook", "domain": "game.example.org", "webhook": { "port": 8789, "secretFile": "webhook.secret" } } }Ou le mode IMAP :
{ "bridge": { "url": "http://127.0.0.1:8787/", "tokenFile": "email.token" }, "gameId": "signals", "email": { "mode": "imap", "imap": { "host": "imap.example.org", "user": "gate@example.org", "passwordFile": "imap.password", "pollS": 60 } } }
-
Lancez-le :
# dans le dépôt du moteur npm run connector -- email --config email.jsonIl journalise
{"event":"email.started","connector":"email","mode":"webhook"}.
-
Essayez le webhook avec un envoi signé. La signature est
sha256=suivi du HMAC-SHA256, sous le secret, de<horodatage>.<message brut>; l’horodatage est en secondes Unix, à moins de cinq minutes de l’horloge du connecteur :# dans le dossier du connecteur, answer.eml contenant un message brut (en-têtes, une ligne vide, le texte) node -e ' const { createHmac } = require("node:crypto"); const { readFileSync } = require("node:fs"); const body = readFileSync(process.argv[1]); const secret = readFileSync("webhook.secret", "utf8").trim(); const t = Math.floor(Date.now() / 1000); const signature = "sha256=" + createHmac("sha256", secret).update(`${t}.`).update(body).digest("hex"); fetch("http://127.0.0.1:8789/v1/inbound", { method: "POST", body, headers: { "X-Web-Scumm-Timestamp": String(t), "X-Web-Scumm-Signature": signature } }) .then(async (r) => console.log(r.status, await r.text())); ' answer.eml
Un message dont le sujet est
PAIR ABCD2345(le code que montre le jeu) lie son expéditeur :202 {"status":"paired"}. Un message suivant du même expéditeur qui dit « open the door » :202 {"status":"accepted"}. Le même message de nouveau :200 {"status":"duplicate"}.
-
GET http://127.0.0.1:9302/healthrépond 200{"connector":"email","ok":true,"detail":"webhook on port 8789",…}(ou"mailbox polled") ;GET /metricscompte les propositions, acceptations, doublons, refus et entrées rejetées. - Le Bridge journalise
"event":"pairing.confirmed", puis"event":"signal.accepted"avec le signal.
| Symptôme | Cause | Correction |
|---|---|---|
email: "mode" is "webhook" or "imap" au démarrage |
Pas de mode | Définir email.mode
|
email: webhook mode needs "webhook.secretFile" / email: imap mode needs "imap.passwordFile"
|
Le secret manque | Nommer son fichier |
email: "imap.tls": false is allowed for a loopback host only (127.0.0.1, ::1, localhost) |
TLS coupé vers un serveur distant | Garder TLS |
401 {"error":"stale"}
|
L’horodatage manque ou est à plus de cinq minutes | Envoyer des secondes Unix ; vérifier les horloges |
401 {"error":"signature"}
|
Un autre secret, ou le corps a changé en route | Signer les octets exacts envoyés |
422 {"error":"no-player"}
|
L’expéditeur n’a envoyé aucun code d’appairage, et aucune étiquette de destinataire de domain ne nomme un joueur |
S’appairer d’abord, ou définir domain
|
422 {"error":"attachment"} / "too-large" / "unreadable"
|
Un message avec pièce jointe, de plus de 256 Ko, ou refusé par le lecteur | Du texte simple, sans pièce jointe |
422 {"error":"pairing-pairing"}
|
Le code d’appairage est inconnu, expiré ou déjà confirmé | Un nouveau code depuis le jeu |
429 {"error":"rate"}
|
Plus de 120 envois par minute depuis une adresse | Ralentir |
- Mode webhook : rien d’écrit ; le message est lu en mémoire puis abandonné.
- Mode IMAP : des drapeaux dans la boîte. Avec
keepDays: 0(par défaut), un message est supprimé dès que son signal est accepté (ou trouvé déjà accepté) ; aveckeepDays: N, au bout de N jours. Un message refusé pour de bon est signalé (\Flagged) et gardé pour l’opérateur. Un message refusé pour une raison passagère (le Bridge absent ou occupé, un quota) reste non lu et est relu au relevé suivant.
Garantie ·
bridge-email-unseen-when-away· référencée · portée vérifiée par son auteur (IA), pas relue Un message dont le signal n’a pas pu atteindre le Bridge (Bridge absent) reste non lu et est relu par un relevé suivant ; un message refusé pour de bon est signalé.
Arrêtez le processus (SIGTERM). Révoquez son jeton sur le Bridge (revoke --token=<id de révocation>) s’il a fuité,
et donnez-en un nouveau. Rien dans le jeu ne dépend du connecteur : un jeu où letter.door est facultatif continue
sans lui.
La section email :
| Champ | Défaut | Sens |
|---|---|---|
mode |
(obligatoire) |
webhook ou imap
|
domain |
aucun | Le domaine des adresses par joueur <boîte>+p-<16 hex>@<domaine> ; sans lui, les étiquettes ne sont pas lues |
maxBytes |
262 144 (256 Ko) | Le plus gros message brut lu ; jamais plus de 4 Mo, quoi qu’il dise |
linkDays |
30 | Combien de temps un expéditeur qui a envoyé un code d’appairage reste lié à son joueur, en mémoire |
webhook.port, webhook.host
|
—, 127.0.0.1
|
Où écoute POST /v1/inbound
|
webhook.secretFile |
(obligatoire) | Le fichier du secret HMAC |
webhook.toleranceS |
300 | L’écart permis entre l’horodatage et maintenant, en secondes |
webhook.perMinutePerIp |
120 | Envois par minute et par adresse |
imap.host, imap.port
|
—, 993 | Le serveur de la boîte, en TLS |
imap.user, imap.passwordFile
|
(obligatoires) | Son identifiant |
imap.mailbox |
INBOX |
Le dossier relevé |
imap.tls |
true |
false seulement vers cette machine |
imap.pollS |
60 | Secondes entre deux relevés (50 messages non lus au plus chacun) |
imap.keepDays |
0 | 0 : supprimer une fois accepté ; N : garder N jours |
- Dans un thread de travail avec un tas de 64 Mo et 2 secondes ; 256 Ko au plus, 3 niveaux de multipart, 64 parties,
200 en-têtes ; le HTML devient du texte inerte ; un message avec pièce jointe est refusé ; un message sans
Message-IDest refusé. - Le joueur est l’étiquette du destinataire (
gate+p-…@<domaine>, avecdomaindéfini) ou l’expéditeur qui a envoyé un code d’appairage (sujetPAIR ABCD2345,LINK …,CODE …ou le code seul). - La première réponse dont tous les mots figurent dans le sujet ou le texte (mots entiers, casse indifférente)
propose son signal, sinon
otherwise. - La clé est
sha256('email:' + Message-ID): un message livré deux fois fait un seul signal. La charge est une empreinte duMessage-IDet du numéro de la réponse, empreinte reprise enevidenceHash. - Aucune réponse n’est envoyée.
- Mode webhook : rien ; le message est lu en mémoire puis abandonné.
-
Mode IMAP : le message reste dans la boîte de l’opérateur, là où il est arrivé, jusqu’à ce que
keepDaysle supprime ; un message refusé reste signalé jusqu’à ce que l’opérateur le supprime. - Un expéditeur qui a envoyé un code d’appairage est retenu sous la forme d’un SHA-256 salé de son adresse (le sel est
aléatoire et vit aussi longtemps que le processus) associé au
playerId, en mémoire, pendantlinkDays; un redémarrage l’oublie. - L’adresse, le sujet et le texte ne sont jamais journalisés ni envoyés au Bridge.
- Le code :
connectors/src/email/connector.ts,imap.ts,mime.ts. - Le modèle de menace :
docs/dev/threat-models/email.md. - Testé par
tests/connectors-email.test.ts.
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Démarrer (6)
Jouer (9)
Installer (17)
- Prérequis
- Installer depuis l'archive de la release
- Installer depuis npm, une fois publié
- Installer depuis le dépôt pour travailler sur le moteur
- doctor : vérifier sa machine
- Outils pour les images
- Outils audio : ffmpeg, fluidsynth, Furnace, uv
- Mettre à jour
- Notes de mise à jour : 4.4
- Notes de mise à jour : 4.3
- Notes de mise à jour : 4.2
- Notes de mise à jour : 4.1
- Notes de mise à jour : v3
- Notes de mise à jour : v2 vers v3
- Venir de web-scumm
- Référence du paquet
- Dépannage de l'installation
Créer un jeu (55)
- Votre premier jeu
- Organisation d'un projet créé et du dépôt du moteur
- La boucle de travail
- Principes du contenu et contrat d'identité
- Salles
- Géométrie : placement, zones de marche, masques, calques, caméra
- Un objet est visible mais inaccessible
- Réactions
- Commandes du jeu
- Conditions
- Drapeaux, variables et événements : lequel choisir
- Code sur mesure
- Personnages
- Objets et inventaire
- Carte du monde
- Dialogues
- Scripts et événements
- Faire bouger les personnages : patrouilles et marches
- Sorties
- Chapitres et objectifs
- État et sauvegardes : persistant ou temporaire, renommer un id, migrations
- Mise en scène : salles larges et animations
- La scène : lumières, particules, sols
- Habillage et polices
- Titre et générique
- Héros : plusieurs personnages et choix du héros
- Mini-jeux intégrés
- Un mini-jeu sur mesure
- Traductions
- Musique en stems
- Bruitages
- Voix : par langue et par héros
- Images : prompts, découpe des planches, transparence, échelle
- Provenance des assets
- Le storyboard, source de vérité
- Concevoir des énigmes
- Mécaniques classiques
- Pages de relecture
- Référence du DSL
- Vérifier son jeu
- Ce que prouve le solveur
- Options du solveur
- Playtests
- Remix
- Speedrun pour les auteurs
- La fin scellée
- Modèle de production
- Jeux d'exemple
- Travailler avec une IA
- Construire et publier un jeu
- Publier sur GitHub Pages
- Le jeu statique sur Clever Cloud
- Autres hébergeurs statiques
- Sous-chemin, cache et service worker, déploiement incomplet, retour arrière
- Tester sur un téléphone en réseau local
Studio local (24)
- Le Studio local : présentation et lancement
- Modifier une salle
- Placement et scène
- Aperçu, application, annulation
- Storyboard
- Assets et découpe
- Vérifier : validateur, solveur, chemin critique, chaleur, santé, couverture
- Captures avant et après
- Diff sémantique
- Jouer et rejouer une session
- Explication des règles
- Langue et objectifs
- Voix et matrice héros × langues
- Carte
- Notes
- Remix dans le Studio
- Musique
- L'Assistant : votre propre clé
- Mode démo et studio-apply
- Sécurité en local et en réseau local
- API du Studio
- MCP local : configurer chaque client
- Outils MCP
- Dépannage du Studio
Studio d'équipe (21)
- Ce qui tourne où
- Concepts : révision, transaction, pan, bail, aperçu, build
- Débuter comme dev invité
- Travailler ensemble au quotidien
- Rôles et actions : la matrice complète
- Invitations
- Gérer les membres
- Écriture refusée sur le Studio hébergé : révision de base périmée (409)
- Écriture refusée sur le Studio hébergé : pan verrouillé (423)
- Reprendre après un bail perdu, une déconnexion ou un aperçu périmé
- Commentaires
- Carte et diff sémantique
- Publier des builds
- MCP distant
- Le jeu dans Git
- Mettre à jour le moteur d'un jeu hébergé
- Assets hébergés
- La ligne de commande du serveur
- Routes de santé et de métriques
- L'essayer en local avec compose.yml
- Limites connues
Clever Cloud (23)
- Clever Cloud : vue d'ensemble, coût et prérequis
- Applications et add-ons
- Domaines et TLS
- Keycloak
- OIDC générique et GitLab
- Configuration
- Premier déploiement
- Sauvegardes
- RPO et RTO
- Exercice de restauration sur une installation vierge
- Redéploiements
- Mettre à jour le serveur et vérifier l'état après une migration
- Limites du retour arrière
- Supervision
- Sécurité
- Renouveler les clés
- Récupérer l'accès administrateur
- Changer de domaine ou de fournisseur OIDC
- Worker, PostgreSQL ou Cellar en panne
- Exporter le jeu et quitter l'installation
- Rétention
- Dépannage sur Clever Cloud
- Coûts et démontage
Reality Bridge et connecteurs (16)
- Le Reality Bridge : ce que c'est
- Démarrage rapide
- Garanties
- Installer et lancer le Bridge
- Sauvegarde et restauration
- Plusieurs tenants
- Signaux et manifeste
- La ligne de commande du Bridge
- Vue d'ensemble des connecteurs
- Connecteur email
- Connecteur Telnet
- Connecteur SSH
- Connecteur Open Badges
- Le SDK des connecteurs
- Sécurité : Biscuit et signatures
- Dépannage du Bridge et des connecteurs
Contribuer (25)
- Bienvenue
- Préparer son poste de développement
- La boucle de développement
- Avant une pull request
- Conventions
- Écrire la documentation
- Architecture
- Visite du code
- Frontières d'import
- Quels tests tournent quand
- Tests de mutation
- Le cliquet de couverture
- La référence de qualité
- La CI expliquée
- Passes de terrain
- Mesures de performance
- Publier une release
- Paquets npm
- Décisions et ADR
- Gouvernance
- Pull requests des bots
- Contribuer avec des agents IA
- Politique de sécurité
- Code de conduite
- Licences et crédits