Rappelio
Un CRM écrit pour un client, que nous avons reconstruit, sécurisé et mis sur le marché comme un produit.

Au départ, un CRM en JavaScript sans framework, fait pour un seul client. À l'arrivée, un SaaS avec ses abonnements, son IA et son site.
L'outil marchait, mais il n'était pas fait pour grandir : données fragiles, interface de bureau, aucun test. Porte 7 a mené le chantier de bout en bout, sans jamais couper l'accès au client d'origine. Voici ce que nous avons fait, dans l'ordre.
- Refonte React
- Sécurité et tests
- Assistant IA
- Abonnements Stripe
- RGPD dans le code
- Marque et site vitrine
- Hébergement et CI/CD
Stabiliser avant de toucher à l'interface.
Refaire un front sur des données qui se perdent, c'est déplacer le problème. On a d'abord réparé le socle.
- Une synchronisation qui ne perd plus rien
- Les contacts vivaient en double, dans le navigateur et dans Strapi. On a écrit une fusion qui garde toujours la version la plus récente, et paginé les appels.
- Un stockage local chiffré
- Passage de localStorage à IndexedDB, avec une clé de chiffrement dérivée d'un secret serveur plutôt que stockée à côté des données.
- Un filet de tests avant la refonte
- Quatre-vingt-dix tests Vitest sur les fonctions critiques, posés avant de réécrire quoi que ce soit. Il y en a 145 aujourd'hui, facturation comprise.
Réécrire en React sans jamais couper le service.
React 19, Vite, TypeScript et TanStack Query. Pour que le client continue de travailler pendant la refonte, le serveur sait servir l'ancien front ou le nouveau : une seule variable d'environnement fait la bascule, dans un sens comme dans l'autre.
front/src/server/server.js
const useReactFrontend = process.env.REACT_FRONTEND === 'true';
const clientDir = useReactFrontend
? path.join(__dirname, '../../dist') // React build (Vite)
: path.join(__dirname, '../client'); // Vanilla JS (legacy)- Écran par écran, jusqu'à la parité complète
- Recette sur l'ancien et le nouveau côte à côte
- Bascule en production, l'ancien front gardé en secours
L'app réécrite, filmée sur une instance locale avec des données fictives.
Penser le terrain parce qu'un contact se prend debout.
Un indépendant rencontre ses prospects sur un salon, pas derrière un bureau. On a repris l'interface pour le téléphone.
L'import multi-format : Excel, CSV ou fiche vCard, colonnes reconnues par leur en-tête.
Des cartes au lieu d'un tableau
Un audit responsive ligne par ligne, puis des cartes contacts pour le mobile : zones tactiles élargies, barre d'outils collante, statut lisible d'un coup d'œil.
Une vraie app, sans store
Rappelio s'installe depuis le navigateur. Une fois installé, il apparaît dans le menu Partager des Contacts du téléphone : un contact s'importe en deux gestes.
Un premier import qui guide
Un compte vide, c'est un utilisateur perdu. L'écran d'accueil explique comment exporter ses contacts, avec les étapes propres à iPhone ou à Android.
Encadrer l'IA pour qu'elle propose sans décider.
Un assistant qui modifie un fichier client tout seul, personne n'en veut. Nous lui avons donné huit outils pour lire et seize pour proposer. Chaque proposition revient en carte Confirmer ou Annuler, et seul ce clic déclenche l'écriture.
Ce qu'il peut lire
search_contactsget_contact_detailslist_reminderslist_contacts_by_statuslist_contacts_by_taglist_stale_contactslist_recent_contactspipeline_stats
Ce qu'il peut seulement proposer
propose_create_contactpropose_create_contacts_batchpropose_update_contact_fieldpropose_update_statuspropose_convert_to_clientpropose_delete_contactpropose_add_tagpropose_remove_tagpropose_add_tag_batchpropose_create_reminderpropose_reschedule_reminderpropose_mark_reminder_donepropose_delete_reminderpropose_reschedule_reminders_batchpropose_add_history_entrypropose_update_next_action
- La voix, de bout en bout
- Dictée par Whisper, réponse lue à voix haute, et micro qui s'arrête seul quand on se tait.
- Des quotas tenus par le serveur
- L'assistant et les scans de cartes sont bloqués côté serveur selon l'offre, pas seulement masqués dans l'interface.
- Un ton écrit
- Son prompt fixe sa personnalité : il tutoie, fait court, et n'invente jamais un identifiant de contact.
Monter le socle qu'un SaaS ne peut pas oublier.
Vendre un abonnement, c'est aussi encaisser, vérifier, piloter et mettre à jour sans casser. Rien de visible pour l'utilisateur, tout pour que ça tienne.
Paiement Stripe
Checkout, portail client, et des webhooks idempotents enregistrés en base : un redémarrage ne facture jamais deux fois.
Inscription vérifiée
Double validation par e-mail et piège à robots sur le formulaire, sans captcha pour l'utilisateur.
Un panneau super admin
Utilisateurs, offres, quotas et revenu mensuel. On peut offrir des crédits IA à un client en un clic.
Des mises à jour invisibles
Pendant un redéploiement, le service worker affiche une page de maintenance au lieu d'une erreur.
Une app, deux adresses
Le client d'origine garde easy-crm.agencerjs.com, le produit vit sur app.rappelio.com. Un seul code, un seul déploiement.
S'auditer avant que quelqu'un d'autre ne le fasse.
Une fois l'app en production, on a passé tout le code au crible : serveur, front et back. Une centaine de constats, quinze jugés bloquants, puis les corrections.
| Point | Avant | Après |
|---|---|---|
| Journal d'audit RGPD | gardé en mémoire, perdu à chaque redémarrage | enregistré dans Strapi |
| Webhooks Stripe | idempotence en mémoire, risque de double abonnement | événements tracés en base |
| Requêtes vers Strapi | relations chargées à la demande du client | liste blanche stricte |
| Politique de sécurité | scripts inline autorisés, héritage de l'ancien front | unsafe-inline retiré |
| Routes de debug | accessibles sans authentification | supprimées |
Le RGPD suit la même logique : les durées de conservation sont écrites dans le code et une tâche quotidienne les applique. Contacts supprimés purgés à 30 jours, comptes inactifs prévenus à 12 mois puis supprimés à 24.
Le mettre sur le marché avec un nom, un site et une mise en ligne sûre.
- Un nom et une marque
- Le projet s'appelait Flow'UP. On l'a rebaptisé Rappelio, dessiné la fusée et renommé l'app, la PWA et les e-mails.
- Un site qui ne ment pas
- Les maquettes du site vitrine sont redessinées à partir du vrai code de l'app, et les tarifs se synchronisent depuis l'app : une seule source de vérité.
- Une mise en production sûre
- Images Docker construites en CI, déploiement avec retour arrière automatique, sauvegarde quotidienne de la base et procédure de restauration écrite.

Le site vitrine, en Next.js statique : nous avons écrit sa promesse, « Relancez. Au bon moment. À chaque fois. »
