Secret Santa 2026 : Slack Connect, exclusions et un coup de neuf
Chaque année depuis 2015, au moment où les premières guirlandes s’allument, Secret Santa reprend du service. Le principe n’a pas changé : vous vous connectez avec Slack, Discord ou Webex, vous choisissez les participants et chacun reçoit en message privé le nom de la personne à qui offrir un cadeau. Pas de compte, pas d’inscription, pas de pub et pas de base de données non plus : le tirage vit dans votre session et disparaît quelques heures plus tard.
Dix ans plus tard, le compteur affiche un peu plus de 30 000 tirages et 300 000 participants depuis que nous les comptons (octobre 2019), avec un record à 474 personnes dans un seul tirage. Chaque mois de novembre, l’application encaisse autour de 1 500 tirages, plus que les neuf premiers mois de l’année réunis (pas très surprenant en même temps 🙃).
Côté code, 2026 a été l’année la plus active du dépôt depuis 2021. Deux demandes ouvertes en 2019 ont enfin été fermées : exclure certaines paires du tirage, et faire participer des gens de plusieurs entreprises. Le site a changé de tête, et sous le capot, nous avons profité de l’occasion pour remettre toute la stack à niveau.
Je vous propose un tour des nouveautés, celles que vous verrez en lançant votre tirage d’abord, puis celles que seuls les contributeurs remarqueront. Pour vous donner envie de lire jusqu’au bout : l’une de ces fonctionnalités a été livrée, désactivée deux jours plus tard, et a attendu près de dix mois avant de revenir pour de bon.
Section intitulée un-tirage-entre-plusieurs-workspaces-slackUn tirage entre plusieurs workspaces Slack
L’idée était simple et la demande datait de 2019 : permettre de faire des tirages avec des participants provenant d’organisations Slack différentes. Plutôt que de complexifier le fonctionnement de l’app (par exemple en imaginant un système avec un login pour chaque organisation désirée), nous nous sommes appuyés sur Slack Connect. Ce système permet, nativement dans Slack, de partager des channels entre plusieurs organisations. Dans notre cas, nous partageons notre Slack (et nos locaux) avec Premier Octet donc le fonctionnement serait idéal.
L’organisateur du Secret Santa va pouvoir ajouter à son tirage tous les membres externes avec lesquels il partage au moins un channel. Concrètement, après la connexion Slack, une nouvelle étape « Channels » s’intercale avant la liste des participants. Deux modes sont présentés côte à côte :
- soit tout le workspace, comme avant (donc uniquement les membres de son propre workspace) ;
- soit uniquement les membres des canaux sélectionnés. Un canal partagé avec un autre workspace amène ainsi tous ses membres (qu’ils soient internes ou externes à votre organisation).

Sur le papier c’est simple. En pratique, Slack a un modèle de permissions qu’il faut respecter à la lettre, et nous avons appris trois choses au passage.
La liste des canaux proposés doit être celle de l’administrateur, pas la liste complète de l’organisation. Nous passons par users.conversations pour l’utilisateur connecté : il ne voit que les canaux dont il est membre, et les canaux privés uniquement si le bot y a aussi été invité. C’est le modèle de confidentialité de Slack, et c’est très bien ainsi : Secret Santa ne doit pas permettre de lister des gens qu’on ne verrait pas soi-même.
Un bot ne peut écrire à un utilisateur externe que s’il partage un canal avec lui. Si vous sélectionnez un canal partagé où Secret Santa n’a pas encore été invité, l’application vous le dit, vous explique quoi faire, et vous laisse rafraîchir la page une fois l’invitation faite. Autre subtilité : poster directement sur l’identifiant d’un utilisateur externe échoue avec channel_not_found. Il faut d’abord ouvrir un DM avec conversations.open, puis poster dans la conversation obtenue.
Les nouveaux scopes ne s’appliquent pas rétroactivement. Cette nouvelle fonctionnalité demande quatre permissions de plus du côté de l’application Slack (channels:read, groups:read, im:write, team:read). Un workspace qui avait approuvé l’ancienne version de l’application connecte ses utilisateurs avec un token qui ne les a pas, et nous l’avons découvert sur notre préproduction avec une belle erreur 500 à l’étape des canaux. Les scopes effectivement accordés sont maintenant vérifiés dès l’authentification, avec un message qui demande à un administrateur du workspace d’approuver les nouvelles permissions.
Les membres externes, absents de users.list, sont chargés via users.info par paquets de 25 dans la boucle de chargement existante, celle qui affiche déjà une barre de progression pour les grosses équipes. Chaque canal sélectionné devient aussi un groupe sur la page des participants, pour cocher ou décocher toute une équipe d’un clic.
Passons à la seconde demande historique.
Section intitulée les-exclusions-pour-de-vrai-cette-foisLes exclusions, pour de vrai cette fois
C’est une des fonctionnalités les plus réclamées du projet et le ticket datait là aussi de fin 2019 : empêcher certaines associations. Un couple qui ne veut pas se tirer l’un l’autre, un manager et son équipe, deux personnes qui se sont déjà offert un cadeau l’an dernier. L’important étant de ne jamais révéler le tirage lui-même : on déclare des règles, pas des résultats.
Nous l’avons livrée fin octobre 2025, juste avant la saison, pour notre propre tirage interne. Et nous l’avons désactivée deux jours plus tard 😝. Le tirage était correct au sens strict, chaque personne offrait bien à quelqu’un et recevait d’une seule personne, mais l’algorithme pouvait produire plusieurs boucles : Alice offre à Bob qui offre à Alice, et les huit autres forment une seconde ronde dans leur coin. C’était techniquement valide, mais humainement décevant : selon moi, le charme d’un Secret Santa tient au fait que tout le monde est relié dans une seule grande chaîne.
Sans exclusion, le problème n’existe pas : on mélange la liste et chacun offre au suivant, le dernier offrant au premier. Une seule ronde, par construction. Dès qu’on interdit certaines paires, cette astuce ne marche plus : le suivant dans la liste peut être justement la personne exclue, et il faut trouver un ordre qui évite toutes les interdictions tout en refermant la boucle sur le premier.
La version qui tourne depuis août 2026 construit la ronde pas à pas. On part d’une personne, on lui choisit au hasard un receveur autorisé parmi ceux qui ne sont pas encore dans la chaîne, et on recommence depuis ce receveur. Quand on tombe dans une impasse, une personne dont tous les receveurs possibles sont déjà pris, on revient d’un cran en arrière et on essaie un autre choix. Arrivé au bout, le dernier doit encore avoir le droit d’offrir au premier, sinon on recule aussi. Le hasard à chaque étape garantit que deux tirages avec les mêmes participants et les mêmes exclusions ne donnent pas le même résultat.
Pour les curieux, le nom savant de ce qu’on cherche est un cycle hamiltonien dans le graphe des associations autorisées, et la méthode du « on essaie, on se coince, on recule » s’appelle le backtracking. Rien de neuf sous le sapin, mais ça aide à savoir quoi chercher.
Ce genre de recherche peut exploser sur des exclusions tordues, par exemple une personne exclue par presque tout le monde. Plutôt que de laisser PHP mouliner jusqu’au timeout, on compte les tentatives et on abandonne au-delà de 100 000, puis on relance depuis un autre point de départ, dix fois au maximum. Si rien ne sort, l’organisateur reçoit un message clair qui l’invite à revoir ses exclusions, plutôt qu’une page d’erreur.
Un test randomisé verrouille le tout : il génère des participants, cache une ronde valide au milieu d’exclusions arbitraires, et vérifie que le résultat forme toujours une seule boucle.
Depuis la réactivation, plus de 180 tirages ont utilisé des exclusions. Et pour les gros tirages, la boîte de dialogue d’ajout d’exclusion a été revue en octobre : recherche par nom et sélection multiple en un clic.

Section intitulée une-nouvelle-robeUne nouvelle robe
Le design n’avait pas vraiment bougé depuis les débuts et ça commençait à se voir : rouge tomate, flocons et cadeaux en emoji, une capture d’écran de l’application en guise d’illustration, et un « Why do I need this app? » qui racontait notre vie. Tout le site a été repris, les pages publiques comme le parcours du tirage et les pages d’erreur.
Le nouveau rouge est plus profond et posé à plat, sans dégradé ni motif. Le reste de la page est blanc, avec des cartes à ombre douce pour les blocs de contenu, et du jaune pour les accents : les badges « free, open-source, no registration », le « your team » du titre, les icônes des trois promesses en bas de page. Le hero montre directement le message privé qu’un participant reçoit, plutôt qu’une capture de l’interface. Les emoji ont laissé la place à une frise numérotée qui déroule les cinq étapes, exclusions comprises, avec un extrait réel de l’interface pour chacune. Le parcours du tirage troque son fil d’Ariane en chevrons pour un stepper à pastilles numérotées.

Un lifting est aussi le bon moment pour passer un audit d’accessibilité. Nous l’avons fait sur le nouveau design avant de le mettre en ligne, et il a corrigé plusieurs choses que nous n’aurions pas vues seuls : les liens dans le texte sont de nouveau soulignés, le contenu est dans un vrai <main>, les textes secondaires sur fond rouge et le gris clair passent le contraste AA, les cibles du menu font 44 pixels de haut et les liens externes annoncent qu’ils ouvrent un nouvel onglet.
Section intitulée les-petites-choses-qui-comptentLes petites choses qui comptent
Au-delà des trois gros chantiers, une poignée de corrections qui changent la vie des organisateurs :
- l’envoi ne s’arrête plus au premier échec : Un participant qui refuse les messages privés sur Discord bloquait tous ceux qui venaient après lui dans la liste, et la page de fin les affichait tous comme « pas encore reçu » alors qu’un seul avait été tenté. Le ticket Discord datait de 2023. L’envoi continue désormais, et « Retry » ne retente que les échecs ;
- une vraie page quand la connexion OAuth échoue : Quand Slack ou Discord répondait une erreur pendant l’échange du token, nous affichions notre page d’erreur générique, comme si l’application avait planté. La panne est presque toujours transitoire et côté fournisseur : la nouvelle page le dit en clair, propose de réessayer, et donne la cause de l’échec en termes compréhensibles ;
- recommencer depuis l’accueil repart de zéro : Un tirage abandonné en route laissait son contexte en session, et le bouton de la page d’accueil renvoyait directement à l’étape des participants, avec les canaux de la fois précédente. Chaque nouveau tirage repasse maintenant par la première étape ;
- la politique de confidentialité détaille maintenant ce que nous collectons, et ce que nous n’en faisons pas. L’occasion de rappeler le principe qui n’a pas changé en dix ans : l’application n’a pas de base de données. La liste des participants, le message et le résultat du tirage vivent dans votre session, stockée dans Redis pendant trois heures, puis tout est effacé. Il ne reste que des compteurs anonymes. Nous affichons d’ailleurs désormais le total de tirages effectués sur la page d’accueil.
Section intitulée sous-le-capotSous le capot
Et pour ceux qui mettent les mains dans le code, le ménage a été fait aussi.
Section intitulée toute-la-stack-a-jourToute la stack à jour
L’application tourne désormais sur PHP 8.5 et Symfony 8.1. Les deux montées de version sont venues ensemble, un peu forcées : Symfony 8.0 arrivait en fin de vie avec une CVE non corrigée sur http-foundation, et phpspec 8.1 refusait PHP 8.5. Nous en avons profité pour supprimer les forks maison de quelques dépendances que nous traînions depuis des années, et pour remettre la stack Docker locale au niveau de notre docker-starter.
Section intitulée assetmapper-plutot-que-des-scripts-inlineAssetMapper plutôt que des scripts inline
Le front, c’était jusqu’ici des blocs <script> dans les templates Twig et des librairies vendorées à la main dans public/. Tout est passé sous AssetMapper : un module ES par page et des helpers partagés, les données passées par attributs data-*, une feuille de style par famille de pages, Bugsnag ou flatpickr déclarés dans l’importmap et téléchargés à l’installation. Chaque fichier est versionné par un digest dans son nom, ce qui a permis de supprimer notre mécanisme maison de version des assets.
Notre CSP est stricte : un script ne s’exécute que s’il porte le nonce généré pour la requête, et rien ne se charge depuis un domaine tiers. AssetMapper n’avait pas prévu ça à deux endroits :
- par défaut, il injecte dans la page un polyfill pour les navigateurs qui ne comprennent pas les importmaps, chargé depuis un CDN. La CSP le bloque, et de toute façon tous les navigateurs que nous supportons comprennent les importmaps nativement : nous l’avons désactivé. Le reste des balises
<script>générées reçoit le nonce via l’option prévue pour ça dans la fonction Twigimportmap(); - un
import './style.css'dans un module JavaScript est transformé par AssetMapper en un faux module qui ajoute la feuille de style au document, servi sous forme d’URLdata:. Là encore, la CSP refuse. Les feuilles de style des librairies, celle de flatpickr en l’occurrence, sont donc liées classiquement avec un<link>dans le template plutôt qu’importées depuis le JavaScript.
Info
Clever Cloud, qui nous héberge gracieusement, lance composer install avec --no-scripts. Les commandes importmap:install et asset-map:compile tournent donc dans un hook de post-build, déclaré par la variable d’environnement CC_POST_BUILD_HOOK.
Section intitulée des-tests-de-bout-en-bout-captures-comprisesDes tests de bout en bout, captures comprises
Le projet avait des tests unitaires et fonctionnels, mais rien ne déroulait un tirage complet dans un vrai navigateur. C’est chose faite avec playwright-php : un parcours entier pour chacune des trois plateformes, contre des API mockées, de la connexion jusqu’à la page de fin.
Et comme nous allions refaire tout le design, nous avons ajouté juste avant des tests de régression visuelle : des captures pleine page de chaque étape et de chaque page publique, comparées à des références versionnées dans le dépôt. Notre précédent article détaillait la méthode avec le Playwright de Node ; À l’époque, playwright-php n’avait pas d’équivalent de toHaveScreenshot() alors la comparaison se fait en PHP avec gd. Un chemin rapide quand les fichiers sont identiques à l’octet, sinon un diff pixel à pixel avec une petite tolérance, et les images attendue, obtenue et différence déposées en artefact de CI quand ça casse.
Info
Cinq jours après notre merge, la version 1.6.0 de playwright-php a livré exactement cette assertion, baselines, masques et images de diff compris. Notre trait maison aura vécu un mois, et c’est très bien ainsi : la prochaine étape sera de le supprimer.
Les références sont rendues par un Chromium installé dans l’image Docker, donc reproductibles uniquement depuis celle-ci, et une task Castor les régénère après un changement d’interface voulu. La refonte graphique a été la première à en profiter : chaque page a été relue capture par capture avant de partir en production.
Section intitulée le-mot-de-la-finLe mot de la fin
Nous avons vu dans cet article ce que 2026 a apporté à Secret Santa :
- des exclusions qui garantissent une seule ronde, avec un algorithme qui sait revenir en arrière quand il se coince ;
- des tirages entre plusieurs workspaces Slack via les canaux partagés, en respectant le modèle de permissions de Slack ;
- un design remis au goût du jour ;
- des envois qui ne s’arrêtent plus au premier participant récalcitrant ;
- PHP 8.5, Symfony 8.1, AssetMapper, et une suite de tests de bout en bout avec régression visuelle pour ne plus livrer une étape cassée sans le voir.
Le code est sur GitHub sous licence MIT, les tickets sont ouverts, et si votre équipe utilise Secret Santa, le Hall of Fame n’attend que vous. Bon tirage 🎁
Commentaires et discussions
Nos articles sur le même sujet
Introducing Secret Santa for Webex by Cisco
A new way to spread joy in the hybrid work experience 🎅 The holiday season is here, and we know how challenging it can be to celebrate with your coworkers. That’s why we are excited to announce the launch of Secret…
par Damien Alexandre
A Slack application to launch a Secret Santa
At JoliCode, we aim to keep a really great atmosphere in our offices and between our collaborators. In addition to our frequent JoliDrinks©, we organize a lot of internal events and Christmas is no exception. Each…
par Loïck Piera