C'est en ligne.
POST /api/v1/mails accepte pieces_jointes, avec le nom de champ que tu proposais — contenu_base64 dit son encodage, donc personne n'y met d'octets bruts. contenu reste accepté, c'est celui qu'on essaie d'instinct.
curl -X POST https://letock.fr/api/v1/mails \
-H "Authorization: Bearer tock_…" -H "Content-Type: application/json" \
-d '{
"de": "Facturation <facture@mail.faktify.fr>",
"a": "client@exemple.fr",
"sujet": "Votre facture F-2026-0042",
"texte": "Bonjour, votre facture est jointe.",
"pieces_jointes": [
{ "nom": "facture-F-2026-0042.pdf", "type": "application/pdf", "contenu_base64": "JVBERi0xLjQ..." }
]
}'
Les limites. Dix fichiers, dix mégaoctets bruts au total — le cumul, pas l'unité. Le corps de la requête monte à 16 Mo sur cette route seule : le base64 gonfle d'un tiers, donc 10 Mo de fichiers en pèsent 13,4 une fois encodés. C'était d'ailleurs le premier mur, avant même l'absence de pièces jointes : toute requête était plafonnée à 64 ko, soit moins qu'une facture PDF encodée.
Sur l'idempotence, tu avais raison et tu as vu plus loin que nous. Une clé réutilisée renvoyait le message d'origine — correct pour un réessai après une coupure. Mais avec une autre facture sous la même clé, tu aurais lu « c'est parti » pendant que la facture précédente repartait, chez un client qui n'avait rien demandé. C'était le seul cas où notre silence expédiait le mauvais document. Une empreinte des fichiers (nom, type, octets, dans l'ordre) voyage maintenant avec le message, et l'écart répond 409 idempotence_divergente au lieu de se taire. Un vrai réessai, fichiers identiques, passe comme avant. Une clé par document : ton numéro de facture en fait une très bonne.
Un écart avec ta proposition, et pourquoi. Tu suggérais de limiter à application/pdf pour commencer. On a pris l'autre bout : tout est accepté sauf une quarantaine d'extensions exécutables (.exe, .js, .bat, .msi…). La protection visée est la même — ce sont exactement celles que Gmail et Outlook rejettent, et leur refus emporte le message entier, facture comprise — mais elle ne bloque pas le client qui voudra joindre un CSV ou une image. Notre refus arrive à l'appel, là où il s'explique, plutôt que chez le destinataire, où il est muet.
Les refus, tous rendus avant la mise en file — jamais une 202 suivie d'un message qui ne part pas :
| code | quand |
|---|
422 piece_jointe_invalide | nom absent ou trop long, type qui n'est pas un type MIME, base64 invalide, trop de fichiers. Le message nomme le rang du fautif |
422 piece_jointe_refusee | extension exécutable |
413 pieces_jointes_trop_lourdes | au-delà de 10 Mo cumulés |
409 idempotence_divergente | même clé, autres fichiers |
Deux détails qui ne se voient pas mais qui décident si un PDF s'ouvre : le base64 est coupé à 76 caractères (une ligne plus longue peut être recoupée n'importe où par un relais, et le fichier arrive corrompu sans que rien ne le signale), et un nom accentué part en RFC 2231 — facture-décembre.pdf arrive avec son accent.
La notice est à jour sur /notice, et l'entrée du 21 septembre au [journal des versions](/changelog) raconte le détail.
Une dernière chose, par honnêteté : nos tests de bout en bout n'ont pas pu tourner sur notre poste au moment de la mise en ligne. La vérification a donc été faite directement en production, en bac à sable — aller-retour octet par octet du fichier, empreinte, refus de divergence. Si quelque chose se comporte autrement chez toi, dis-le ici : on regardera tout de suite.