Menu
Commandes + Cmd SAV & INFO Contacts Comptes pro Chiffrages Idées Messages
Utilisateur
Darsy
Messages
Chargement...
Nouvelle conversation
Sélectionnez un ou plusieurs participants :
Source : docs/40-relation/35-contacts.md — ce document se lit aussi dans le dépôt, sans serveur.

Contacts

Où : Relation → Contacts, et le bouton Contacts de la barre du haut, avec son compteur.

Le formulaire public est sur sav.monsieurcle.fr/contact/, un guichet du mini-site d'aide.

Il a vécu un jour sur contact.monsieurcle.fr, avec sa propre apparence. Ce sous-domaine redirige. Le 27/09/2026.

À quoi ça sert

Monsieur Clé n'est pas un site, c'en est six : monsieurcle.fr, arlet.io, organigramme-de-cle.fr, organigramme.app, cles.app, et Darsy. Chacun avait — ou n'avait pas — son formulaire, et chaque formulaire partait par mail.

Un mail se perd, ne porte pas d'état, et ne se compte pas. Personne ne pouvait répondre à « combien de demandes cette semaine » ni « celle-là, on y a répondu ? ». Une demande de reprise d'organigramme est un acte commercial : elle vaut une ligne, pas un fil de discussion dans une boîte.

Un seul formulaire, un sélecteur de site, et tout arrive ici.


Ce n'est pas le SAV

Part deVit dans
SAVune commande connuecustomer_contacts, rattaché à la commande
Contactpersonne — souvent un prospectcontact_requests, rattaché à rien

Les ranger ensemble aurait obligé chaque lecture du SAV à écarter les lignes sans commande, ce qui est le genre de condition qu'on oublie une fois sur trois.

Le bouton SAV & INFO disait « SAV, demandes d'info et contacts ». Ce n'est plus vrai : les contacts ont leur propre bouton. Les deux libellés ont été corrigés le 27/09/2026.

Les quatre états

ÉtatCe qu'il veut dire
NouveauPersonne ne l'a encore regardée. C'est le seul que compte le badge
En coursQuelqu'un s'en occupe
TraitéRépondu
IgnoréDu spam qui avait l'air humain, ou une demande sans objet

Il n'y a pas d'état « spam » : une demande reconnue comme automatique n'est jamais enregistrée.

Les onglets de l'écran sont les états, parce qu'une demande n'est pas une catégorie à consulter — c'est un travail à faire. Le site, lui, est un filtre : on ne travaille pas par site, on répond dans l'ordre d'arrivée.


Répondre, depuis Darsy

Dans le panneau de la demande : un éditeur — gras, italique, souligné, barré, listes, liens — trois dépôts de pièces jointes, et Envoyer et marquer traitée. Le courriel part de Darsy, avec le sujet de la demande précédé de « Re : » — la personne retrouve sa propre phrase, et non une référence interne qui ne lui dit rien.

C'est le même composeur que la fiche commande et que l'envoi fournisseur : Darsy.courrier, dans main.js. Il n'y en a qu'un.

Jusqu'au 17/10/2026, c'était un <textarea> nu, sans aucune pièce jointe possible. On recevait trois photos d'un client et on ne pouvait pas lui renvoyer un plan. Et ce n'était pas une négligence d'écran : la route lisait du JSON, et envoyait le message en nl2br(htmlspecialchars()). Une mise en forme y serait partie en balises visibles chez le client. Le formulaire était honnête sur ce que le serveur derrière savait faire. Ce qui a changé des deux côtés : la route accepte le multipart, et le corps du courrier passe par Darsy\Mail\CorpsHtml — une liste blanche de balises, et rien d'autre ne traverse.

Le message reste enregistré en texte. Seul le courrier porte la mise en forme : un HTML assaini qui reviendrait s'afficher dans l'écran Contacts serait un deuxième problème, et on ne le crée pas pour en régler un.

Avant le 07/10/2026, c'était un lien mailto:. La réponse partait du client de messagerie du poste, avec une autre signature, et Darsy n'en gardait aucune trace. « Celle-là, on y a répondu ? » — la question que cet écran est censé fermer — restait donc sans réponse, et il fallait fouiller une boîte d'envoi.

Les réponses déjà envoyées sont affichées au-dessus de la zone de saisie. C'est la première chose qu'on veut savoir en rouvrant une demande, et rien d'autre ne le dit.

Rien n'interdit d'en envoyer plusieurs : un échange en demande parfois deux — « pouvez-vous préciser ? », la précision, puis la vraie réponse. Elles vivent dans leur propre table pour cette raison ; une colonne unique aurait fait écraser la première par la seconde, sans que rien ne le dise.

Un envoi qui échoue est enregistré quand même, et la demande reste en cours

Un courriel refusé par le serveur de messagerie doit se voir. Sans cette trace, une réponse écrite mais jamais partie se lirait comme une réponse faite, et le client attendrait pour rien.

L'écran l'affiche alors en rouge, « NON PARTIE », et l'état ne bascule pas : marquer traitée une demande dont la réponse n'est pas partie la ferait quitter la liste de travail sans que personne n'ait été servi.


Le formulaire est public, et il doit l'être

C'est tout son objet : le prospect qui découvre arlet.io n'a pas de compte, et lui en demander un couperait exactement les gens qu'on veut.

Donc aucune identité derrière, et trois bornes à la place :

  1. cinq envois par dix minutes et par adresse IP — une personne qui écrit deux fois de suite passe, un script non ;
  2. un champ invisible qu'un humain ne remplit jamais, et un délai minimum de trois secondes ;
  3. les tailles : 5 000 caractères, trois fichiers, 8 Mo chacun.

Pas de captcha, et c'est un choix

Un captcha externe marche bien — mais c'est un script tiers, donc l'inverse de la politique de sécurité qu'on tient sur nos applications, et une dépendance de plus dans le chemin d'un formulaire qui doit marcher toujours.

On commence sans, et on regarde ce qui passe. Si du spam arrive vraiment, on en ajoute un — en connaissance de cause.

Les limites de ces règles sont écrites dans le code : le délai est déclaré par le navigateur, donc falsifiable. La vraie borne est la limite de débit.

Un robot démasqué reçoit un succès

Et rien n'est enregistré. Lui répondre « refusé » lui apprendrait quel champ éviter au prochain essai.

Une erreur de saisie, elle, se dit clairement : quelqu'un qui a écrit « ok » doit savoir qu'on attend mieux. Les deux cas sont distincts dans le code.


Les pièces jointes

Images et PDF, 8 Mo, trois au plus. Leur type est constaté sur les octets, jamais lu dans l'en-tête annoncé — un vrai PNG posté en image/svg+xml repartirait plus tard en inline avec ce type-là. Même règle que les justificatifs des pros, posée après l'avoir payée une fois.

Elles ont leur propre table. Le formulaire SAV de la boutique écrit la liste de ses fichiers dans le message, derrière [Fichiers joints:, et la fiche commande découpe là-dessus. Ça marche, et c'est ce qu'il ne faut pas refaire : un nom de fichier contenant ce marqueur casse la lecture, et rien ne peut dire « ce fichier a été purgé » sans réécrire le message.

Celles qu'on envoie se rangent avec celles qu'on reçoit

contact_request_files porte une colonne reply_id depuis la migration 059 :

reply_idQui l'a envoyée
NULLle client, avec sa demande
un identifiantnous, avec cette réponse-là

Une seule table, un seul dossier de stockage, une seule route pour les servir, une seule purge. Attention aux lectures : toute requête qui liste « les pièces de la demande » doit porter AND reply_id IS NULL, sinon l'écran montrerait nos propres envois parmi ce que le client a joint. Deux endroits, tous deux dans ContactRequestRepository.

Elles s'ouvrent dans Darsy

Les images sont des vignettes de 72 pixels, le reste des pastilles avec leur nom et leur poids — comme sur une fiche commande. On sait d'un coup d'œil si la photo de la clé est lisible.

Un clic sur une image l'affiche en grand. Un PDF s'ouvre dans un onglet, et c'est le navigateur qui décide de l'afficher ou de le télécharger.

Le PDF s'ouvrait dans un <iframe>, et le cadre restait vide. La route sert la pièce avec Content-Security-Policy: sandbox — c'est ce qui permet de l'afficher sans risquer qu'un PDF fabriqué s'exécute. Mais un document sandboxé dans un <iframe> n'a pas le droit de lancer le lecteur PDF du navigateur. On ne pouvait ni lire la pièce, ni comprendre pourquoi. Corrigé le 17/10/2026.

Une pièce purgée garde sa place, en texte inerte : la faire disparaître laisserait croire que le client n'avait rien joint.

Jusqu'au 07/10/2026, rien ne les servait. Elles étaient reçues, rangées dans storage/, et aucune route n'existait pour les lire : l'écran affichait un nom de fichier en texte inerte. On voyait qu'une photo de clé existait, et on ne pouvait pas la regarder.

Elles sont servies en ligne et non en téléchargement — une photo qu'il faut télécharger puis retrouver dans ses téléchargements n'est pas regardée. Trois choses rendent cela acceptable, et il faut les trois :

  1. le type est constaté sur les octets à la réception, jamais lu dans l'en-tête annoncé ;
  2. seuls JPEG, PNG, WEBP et PDF sont acceptés au dépôt — pas de SVG, qui est du XML exécutable ;
  3. la réponse porte une politique sandbox : même un PDF fabriqué pour exécuter du script n'y arrive pas.

En retirer une rouvrirait la faille que les deux autres ne ferment pas seules.


Ce qu'il faut pour que ça marche

Trois choses doivent s'accorder, et si l'une change les autres doivent suivre :

Le sous-domaine sav.monsieurcle.frpointé sur darsy/public/sav
CORS_ALLOWED_ORIGINS dans le .envdoit lister https://sav.monsieurcle.fr
La directive connect-src de la pagedoit lister l'origine de l'API

Oublier la deuxième donne un formulaire qui s'affiche et un envoi que le navigateur refuse en silence : la panne ressemble alors à un bug du formulaire.

Le formulaire vit dans le dépôt Darsy, donc un git pull de l'ERP le déploie aussi — pas de dossier séparé à ne pas oublier.