
Send Me Now
Messages anonymes ephemeres par code QR, avec boutique en ligne. Mandat client, en production sur sendmenow.ca.
Technologies
A propos du projet
Send Me Now est une messagerie anonyme par code QR doublée d'une boutique en ligne. On colle son QR quelque part, les gens scannent et écrivent, le message s'efface au bout de 24 heures. Je l'ai construit en freelance pour une fondatrice québécoise, backend et frontend, livré fin mars 2026 et en production sur sendmenow.ca.
Le contexte : qui, pourquoi, quelle contrainte
La cliente m'a écrit vers mars 2026, après être passée par ce portfolio. Elle est la fondatrice et la propriétaire du produit ; moi je suis le développeur qu'elle a engagé. Le mandat couvrait le build complet, backend et frontend.
Ce qu'elle voulait n'était pas juste une messagerie. Elle voulait deux choses dans le même produit : un canal où sa communauté peut lui écrire sans se nommer et sans installer d'appli, et une boutique où chaque objet vendu porte un QR rattachable à un compte. Ce dernier point était sa condition explicite : des produits associés à un QR, pas un catalogue collé à côté. Ajoutez trois langues, français, anglais, espagnol.
Trois langues sur une boutique, ce n'est pas une couche de traduction posée sur l'interface. Le nom d'un produit, sa description et ses variantes vivent en base et doivent exister dans les trois langues, sinon la fiche se vide dès qu'un visiteur change de langue. C'est une contrainte de modèle de données, pas de gabarit, et elle se tranche avant d'écrire la première migration.
Le problème : ce qui coinçait vraiment
Quelqu'un scanne, écrit un mot, referme son téléphone. Vingt-quatre heures plus tard, ce message doit avoir disparu : c'est la promesse faite au visiteur. Dans la même application, une conversation, une commande et un jeton QR réclamé doivent survivre indéfiniment et rester auditables. Deux natures de données opposées, un seul backend.
Tout mettre dans Postgres voulait dire écrire un job de purge. Un job de purge qui échoue en silence laisse traîner des messages qui devaient disparaître, et c'est la promesse du produit qui casse. Je ne voulais pas que la durée de vie d'un message dépende de la bonne santé d'un cron.
L'autre contrainte vient du support physique. Le QR imprimé est figé pour toujours, le compte derrière ne l'est pas. Un vinyle d'auto collé chez un client ne peut pas être réimprimé parce que le propriétaire du QR change.
Ce que j'ai construit : les choix techniques et pourquoi ceux-là
Django avec DRF, Channels servi par Daphne pour le temps réel, Celery pour l'asynchrone, Postgres, et Redis en double base.
Le Redis dual DB est le choix central. DB 0 porte les messages éphémères avec un TTL Redis natif de 24 heures : l'expiration devient une propriété de la clé, pas une tâche à exécuter. DB 1 porte le cache, le broker Celery et la couche Channels. Les séparer évite qu'un flush de cache emporte des messages, et évite les collisions de namespace, dont une que j'ai trouvée et corrigée en hotfix.
Le découpage en apps suit la même logique de durée de vie : users (auth email plus JWT, OAuth Google et Apple, profil public, qr_slug), messages (éphémères), conversations (threads persistants, WebSocket, blocage), qr (rendu Pillow maison plus API QR Studio), claimable_qr (jetons physiques, batchs jusqu'à 10 000), shop, stats, notifications (Resend, WebPush, Twilio), moderation.
Le module claimable_qr répond à la contrainte du QR imprimé : lots, tokens, journal d'audit, endpoints REST dédiés, rate limiting anti-abus. Le tag part à l'impression sans propriétaire et se réclame après coup. Le journal d'audit n'est pas décoratif : quand un objet physique change de main, la seule façon de trancher une contestation de propriété est de pouvoir rejouer qui a réclamé quoi et quand.
Stripe Connect Standard plutôt qu'un compte unique, parce que l'argent de la boutique doit tomber dans le compte de la cliente, jamais dans le mien. Ce choix a une conséquence que j'assume : la conformité, la fiscalité et la gestion des litiges de paiement restent chez la propriétaire du commerce, là où elles doivent être. i18n en trois langues via modeltranslation, admin django-unfold, stockage S3 avec bascule automatique vers le disque local quand les identifiants ne sont pas posés, ce qui garde l'environnement de développement utilisable sans compte infonuagique.
Détail que j'ai appris en cartographiant le code après coup : la boutique domine l'application. Les nœuds les plus connectés sont Product, Order et BoutiqueSettings, loin devant la messagerie. Le produit s'appelle Send Me Now, mais techniquement c'est surtout un shop.
Le résultat : ce qui est mesurable et vrai
Build terminé fin mars 2026, en production sur sendmenow.ca.
La plateforme tourne pour de vrai : compte Stripe Connect dédié au nom de la propriétaire, rail de paiement vivant, boutique qui génère des commandes avec courriel de confirmation automatique et bon de commande PDF, assets servis depuis S3.
Ce rail, je l'ai traversé de bout en bout avant de livrer, commande après commande : passage au paiement, écriture de la commande en base, courriel parti, bon de commande généré, fichiers servis depuis S3. Chaque maillon a été exercé sur l'environnement de production, pas sur une maquette locale. Une chaîne de paiement, ça ne se déclare pas finie, ça se traverse. C'est la partie du travail que je refuse de faire vite : un défaut de rendu se voit tout de suite, un défaut de paiement se découvre dans un relevé bancaire un mois plus tard.
Le temps réel a été vérifié de la même façon, deux navigateurs ouverts côte à côte, parce que c'est le seul moyen de voir ce qu'un poste de test unique ne montre jamais : l'ordre d'arrivée des messages, la reconnexion après une coupure, et l'état de la conversation quand un des deux côtés recharge la page.
Mon mandat couvrait la plateforme. La mise en marché n'en faisait pas partie, et c'est aujourd'hui la première chose que je mets sur la table au démarrage d'un projet : qui s'occupe de l'audience, du contenu et de l'acquisition, et à partir de quand. Livrer une infrastructure sans avoir eu cette conversation, c'est livrer la moitié d'un résultat.
Sur la maintenance, je m'en tiens à une règle depuis ce projet : l'état exact des versions et des dépendances d'une application qui encaisse des paiements se discute avec la propriétaire, dans un plan de mise à niveau daté, jamais sur une page publique. Publier la carte détaillée d'un système en production ne rend service à personne d'autre qu'à celui qui cherche une porte.
La propriétaire m'a confié un deuxième produit après celui-là, Find It Now, bâti sur la brique QR sortie d'ici.
Ce que j'en retiens
Quand une promesse produit est une contrainte de durée, il faut la faire porter par l'infrastructure et pas par du code applicatif. Un TTL Redis ne se plante pas, un cron de purge oui. La question à se poser devant chaque règle métier : est-ce que je peux la déplacer dans une propriété du système, plutôt que dans une tâche qui doit s'exécuter correctement pour toujours ?
Deuxième leçon, sur le nom. Un produit ne décrit pas toujours son propre centre de gravité technique. Ici, le graphe de dépendances pointe vers la boutique alors que le nom parle de messagerie. Ça change la façon d'estimer un mandat, parce que l'effort suit le graphe, pas la présentation commerciale.
Côté affaires, la règle que j'applique aujourd'hui tient en deux points : une entente écrite avant la première ligne de code, et un versement à la signature. Tout facturer à la livraison fait porter le risque d'un seul côté pendant tout le build, et ce déséquilibre finit toujours par se payer, d'un bord ou de l'autre. Un premier versement protège autant le client, parce qu'il fixe noir sur blanc le périmètre, la date de livraison et ce qui arrive si l'un des deux change d'idée en cours de route.
La brique claimable QR construite ici sert aujourd'hui de base à QRStudio, mon SaaS de QR dynamiques. C'est ma meilleure défense contre le travail jetable : un module bien découpé sur un mandat devient le socle d'un produit ensuite, à condition de l'avoir écrit comme une app autonome et pas comme une couche collée au projet.

