Envoyer par email et suivre la réception
Une partie de vos destinataires reste hors des réseaux de facturation électronique : clients étrangers, particuliers, structures non raccordées. Pour eux, l'email reste le canal de remise. Scribee envoie vos factures et vos devis par email depuis l'API, avec le document en pièce jointe, et trace la réception adresse par adresse : remis, ouvert, rejeté. Vous savez qui a reçu quoi, sans infrastructure d'envoi de votre côté.
Ce que Scribee fait pour vous
- Composition du message : objet généré à partir du numéro du document (
Facture INV-2024-00156,Devis Q2024-0001), corps dans la langue résolue, votre message personnalisé, et une seule pièce jointe : le Factur-X pour une facture qui en dispose, le PDF lisible sinon. - Un suivi par destinataire : chaque adresse (principale et copies) produit son propre enregistrement de livraison, dont le statut avance au fil des événements de remise, sans action de votre côté.
- Langue du message :
frouen, par défaut la langue de communication enregistrée pour le client.
Le flux
Documents et états éligibles
- Facture : tout état de cycle de vie sauf
draft, dès que son PDF est généré. La liste exacte estdeposited,received,available,taken_in_charge,approved,disputed,refused,payment_sent,collected,rejectedetcancelled. Un brouillon, jamais. - Devis : états
sent,accepted,rejectedetconverted. Un devisdraft,cancelledouexpiredest refusé. - Dans les deux cas, un email de contact de l'acheteur doit être disponible, même si vous fournissez
to_emailexplicitement. Pour une facture, l'adresse portée par l'acheteur du document est prioritaire ; si elle est vide, Scribee utilise l'email du contact par défaut du client lié à cette facture. Pour un devis, l'adresse doit être renseignée sur l'acheteur du document. Cette adresse est une condition d'envoi, jamais un destinataire ajouté automatiquement : les destinataires de cet appel viennent exclusivement deto_emailetcc_emails. - L'adresse de contact de l'acheteur est une copie figée, prise sur la fiche du client au moment où le document a été créé. Elle ne se resynchronise jamais avec l'annuaire : voir la section 422 ci-dessous.
Les envois manuels par cet appel restent distincts de l'envoi automatique de l'original demandé à la validation. Pour les nouveaux envois automatiques, modifier ensuite le contact du client ne déclenche pas un second original lors d'une reprise du traitement. Une tentative échouée reste relançable. Cette protection ne couvre pas les anciens envois enregistrés sans lien avec leur validation : vérifiez leur historique avant de les relancer.
Un état non éligible ou un PDF pas encore généré est refusé en 403, avant toute validation de la charge utile.
Étape 1 : envoyer une facture
Cet appel met immédiatement en file un email réel vers chaque adresse fournie : le 201 signifie que l'envoi est enregistré et confié à la file de traitement, pas qu'il a quitté Scribee. Le rendu et la remise ont lieu ensuite, en tâche de fond, et peuvent échouer après coup - suivez status sur email_deliveries plutôt que de traiter le 201 comme un accusé de remise. Une fois parti, un email ne se rappelle pas. Il n'existe pas d'environnement de test - pour valider votre intégration, adressez le premier envoi à une adresse que vous contrôlez. L'envoi ne modifie ni le cycle de vie ni les données du document, et ne transmet rien au PPF (Portail Public de Facturation) ni au réseau Peppol. Un token read write est requis.
curl -X POST https://app.scribee.tech/api/v1/invoices/YOUR_INVOICE_ID/send_by_email \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email_delivery": {
"to_email": "client@example.com",
"cc_emails": ["compta@example.com"],
"body_text": "Vous trouverez ci-joint votre facture.",
"locale": "fr"
}
}'
Seul to_email est obligatoire. Réponse 201, abrégée aux champs utiles : une livraison par destinataire retenu, toutes partageant le même send_batch_id. Toutes les lignes d'un même lot portent le même subject, la même locale et le même sent_at, quel que soit leur recipient_type.
{
"data": [
{
"id": 87,
"send_batch_id": "550e8400-e29b-41d4-a716-446655440000",
"recipient_type": "to",
"to_email": "client@example.com",
"subject": "Facture INV-2024-00156",
"locale": "fr",
"status": "queued",
"sent_at": "2026-07-31T11:12:04+02:00"
},
{
"id": 88,
"send_batch_id": "550e8400-e29b-41d4-a716-446655440000",
"recipient_type": "cc",
"to_email": "compta@example.com",
"subject": "Facture INV-2024-00156",
"locale": "fr",
"status": "queued",
"sent_at": "2026-07-31T11:12:04+02:00"
}
]
}
Les horodatages sont sérialisés en ISO 8601 avec le décalage du fuseau Europe/Paris (+02:00 en heure d'été, +01:00 en heure d'hiver), jamais en UTC avec un suffixe Z.
Trois comportements à connaître :
- L'objet n'est pas un paramètre. Il est généré à partir du numéro du document, dans la langue résolue.
localeacceptefrouen; toute autre valeur est ramenée àfr. Sans valeur, la langue de communication du client s'applique,frà défaut.- Les doublons d'adresses sont ignorés (comparaison insensible à la casse, adresse principale comprise) : une adresse ne reçoit qu'un exemplaire.
body_textaccepte 5000 caractères au maximum.
Étape 2 : suivre la réception
GET /api/v1/invoices/{id}/email_deliveries retourne l'historique des envois de la facture, les plus récents d'abord (tri sur created_at décroissant, non paramétrable), paginé par page et per_page. Lecture sans effet de bord, un token read suffit. per_page vaut 20 par défaut et est ramené silencieusement dans l'intervalle 1-100 : per_page=500 renvoie 100 lignes sans avertissement.
curl https://app.scribee.tech/api/v1/invoices/YOUR_INVOICE_ID/email_deliveries \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Réponse 200, abrégée aux champs utiles ici. Les objets renvoyés sont les mêmes que ceux du POST de l'étape 1 : mêmes champs, même forme.
{
"data": [
{
"id": 87,
"recipient_type": "to",
"to_email": "client@example.com",
"status": "opened",
"delivered_at": "2026-07-31T11:12:31+02:00",
"first_opened_at": "2026-07-31T12:02:11+02:00",
"last_opened_at": "2026-07-31T16:45:52+02:00",
"failed_reason": null
},
{
"id": 88,
"recipient_type": "cc",
"to_email": "compta@example.com",
"status": "delivered",
"delivered_at": "2026-07-31T11:12:35+02:00",
"first_opened_at": null,
"last_opened_at": null,
"failed_reason": null
}
],
"meta": { "current_page": 1, "per_page": 20, "total_pages": 1, "total_count": 2 }
}
Les statuts possibles :
| Statut | Signification | Champ associé |
|---|---|---|
pending | envoi enregistré, pas encore mis en file | created_at |
queued | mis en file d'envoi | sent_at |
delivered | remis dans la boîte du destinataire | delivered_at |
opened | message ouvert | first_opened_at, last_opened_at |
clicked | un lien du message a été suivi | - |
bounced | rejeté par le serveur du destinataire | failed_reason |
failed | échec d'envoi | failed_reason |
blocked | adresse bloquée à l'envoi | failed_reason |
spam | signalé comme indésirable | - |
pending est l'état à la création des livraisons. Il est remplacé par queued (ou par failed si la mise en file échoue) dans la foulée, avant que la réponse au POST ne soit renvoyée ; la réponse 201 ne le porte donc jamais. Un GET concurrent lancé pendant cette fenêtre peut en revanche l'observer : traitez-le comme un état transitoire, pas comme un état impossible.
Le statut avance et ne recule jamais : opened ne redevient pas delivered, et bounced, failed, blocked et spam sont terminaux. Aucun webhook ne signale ces changements : interrogez l'endpoint au rythme qui convient à votre usage.
sent_at est horodaté au moment où le lot est remis au prestataire d'envoi, et il le reste quel que soit le statut atteint ensuite : une livraison terminale porte donc un sent_at non nul, aux côtés du failed_reason qui accompagne bounced, failed et blocked. Un sent_at renseigné atteste la remise au prestataire, pas la remise au destinataire : c'est delivered_at qui l'atteste.
Étape 3 : envoyer un devis
Les deux mêmes opérations existent sur les devis, avec la même charge utile et les mêmes réponses : POST /api/v1/quotes/{id}/send_by_email et GET /api/v1/quotes/{id}/email_deliveries. L'avertissement de l'étape 1 s'applique à l'identique : l'email est mis en file immédiatement et part sans confirmation supplémentaire.
curl -X POST https://app.scribee.tech/api/v1/quotes/YOUR_QUOTE_ID/send_by_email \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email_delivery": { "to_email": "client@example.com" } }'
L'objet devient Devis Q2024-0001 ; le reste du comportement, statuts de suivi compris, est identique.
Ce que le destinataire reçoit
Un email dans la langue résolue, reprenant les informations du document (numéro, date d'émission, échéance ou date d'expiration, montant TTC) et votre body_text présenté comme message de l'expéditeur.
Le message porte une seule pièce jointe, et laquelle dépend du document :
- Facture disposant d'un Factur-X : ce Factur-X, nommé
INV-2024-00156-facturx.pdf. Un Factur-X est un PDF/A-3 : il s'ouvre dans n'importe quel lecteur et porte le XML CII, donc il tient les deux rôles à lui seul. Quand la facture a été déposée chez Scribee sous forme de Factur-X, ce sont les octets déposés qui partent, à l'identique (Formats et téléchargements). - Facture sans Factur-X, et tout devis : le PDF lisible, nommé d'après le numéro du document (
INV-2024-00156.pdf). Une facture peut légitimement se trouver dans ce cas - génération non encore terminée, ou formatfacturxen échec.
:::caution Changement de comportement
Jusqu'ici le message portait deux pièces jointes pour une facture avec Factur-X : le PDF lisible et le Factur-X. Le PDF lisible n'est plus joint dans ce cas. Si votre traitement s'appuyait sur la présence du fichier <numéro>.pdf, basculez-le sur <numéro>-facturx.pdf, ou récupérez le format voulu via l'endpoint d'export, qui sert toujours les quatre formats.
:::
Ce qui se passe ensuite
- L'email part de l'infrastructure de Scribee peu après la réponse
201, une fois la tâche de rendu exécutée ; les livraisons passent dequeuedaux statuts de remise au fil des événements, sans appel de votre part. - Après un
201, une livraison peut basculer enfailedavecfailed_reasonàdelivery_failed: le rendu ou l'envoi a échoué en tâche de fond. C'est le seul code interne qu'un envoi accepté peut produire. Un rebond du serveur destinataire place la livraison enbounced, et d'autres évènements remontés par le prestataire d'envoi produisentfailedavec le motif qu'il fournit - mais seulement si la livraison n'a pas déjà atteint un statut terminal.bounced,failed,blockedetspamsont de même rang : le premier arrivé fige le statut, et un évènement terminal ultérieur ne peut plus que changerfailed_reason. Une livraison différée puis rebondie reste donc enfailed. Lisezfailed_reasonen plus du statut. enqueue_failedne suit jamais un201: il est écrit quand la mise en file elle-même échoue, et l'appel répond alors422. Les livraisons ont déjà été créées à ce stade, donc l'historique du document les montre enfailedavec ce code - traitez-les comme non envoyées et rejouez l'envoi. L'historique est propre à chaque type de document :GET /api/v1/invoices/{id}/email_deliveriespour une facture,GET /api/v1/quotes/{id}/email_deliveriespour un devis. L'envoi d'un devis n'apparaît jamais sur l'endpoint des factures.- Ces deux codes sont volontairement opaques ; le détail technique reste côté Scribee. Contactez le support avec le
send_batch_idsi l'un d'eux persiste. - Chaque nouvel appel
POSTcrée un nouveau lot (send_batch_id) et de nouvelles livraisons : renvoyer un document est possible à tout moment, et l'historique complet reste consultable suremail_deliveries. - Le document lui-même ne change pas : pas de transition de cycle de vie, pas de transmission réseau. L'envoi par email est indépendant du canal réglementaire décrit dans Le cycle de vie d'une facture.
Erreurs et cas limites
400 Bad Request
Deux cas, tous deux dans l'enveloppe d'erreur habituelle : seul le message change.
Un corps POST sans objet email_delivery n'atteint jamais la validation : la lecture des paramètres échoue avant, et la réponse est un 400 dans l'enveloppe d'erreur habituelle, avec error à bad_request et le message "Le corps de la requête est manquant ou mal formé", sans clé details. Envoyez toujours la charge utile enveloppée dans email_delivery.
Sur GET .../email_deliveries, un page supérieur au nombre de pages disponibles renvoie un 400 avec la même clé bad_request et un message différent :
{
"error": "bad_request",
"message": "Le numéro de page dépasse le nombre de pages disponibles"
}
403 Forbidden : document non éligible
L'état du document interdit l'envoi : facture draft, devis draft, cancelled ou expired, ou PDF pas encore généré. Un 403 signale aussi un token sans les scopes suffisants - l'envoi demande un token read write, la lecture des livraisons un token read.
La réponse porte toujours "error": "forbidden", mais le message diffère selon la cause et n'est pas garanti traduit : basez votre logique sur le statut HTTP et sur error, jamais sur le texte. Vérifiez l'état du document via GET /api/v1/invoices/{id} avant d'envoyer.
404 Not Found
{
"error": "not_found",
"message": "La ressource demandée est introuvable"
}
Quatre situations donnent cette réponse, sans les distinguer :
- Le document n'existe pas.
- Il appartient à un workspace non lié à votre application.
- Il appartient à un workspace lié, mais dont la liste d'IP autorisées exclut l'adresse d'où part l'appel. Ces chemins ne portent pas de
workspace_id, donc le refus de périmètre s'y présente en404et non en403. - C'est une facture de vente, ou un devis, d'une entreprise dont l'offre Scribee ne couvre pas la vente. Les factures d'achat ne sont jamais masquées de cette façon.
422 Unprocessable Entity : la validation a échoué
{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"base": ["L'adresse e-mail du destinataire est invalide"]
}
}
Les messages possibles sous details.base :
| Message | Cause | Ce que vous faites |
|---|---|---|
L'adresse e-mail du destinataire est invalide | to_email mal formé ou absent | corrigez l'adresse |
L'adresse e-mail en copie est invalide : compta.exemple.com | une entrée de cc_emails est mal formée (la première adresse fautive est citée) | corrigez ou retirez l'entrée |
Aucune adresse e-mail de contact n'est associée à l'acheteur | le document porte un acheteur sans adresse de contact | voir ci-dessous |
L'envoi de l'e-mail a échoué. Veuillez réessayer. | la mise en file a échoué ; aucune livraison n'est partie | rejouez l'appel |
Un body_text au-delà de 5000 caractères renvoie aussi 422.
La validation d'adresse est permissive : elle exige un @ et une partie domaine, mais accepte un domaine sans point. compta@exemple passe donc la validation et l'envoi est tenté.
Aucune adresse e-mail de contact n'est associée à l'acheteur : vérifiez le contact de la facture ou du client lié. Pour une facture dont l'acheteur n'a pas de contact_email, renseigner l'email du contact par défaut du client lié permet de rejouer l'envoi. Ce recours à la fiche client ne modifie pas les données copiées sur la facture et ne remplace jamais une adresse déjà renseignée sur celle-ci. Sans client lié, renseignez le contact de l'acheteur pendant que la facture est encore modifiable. Pour un devis, l'adresse reste celle copiée sur le document : compléter la fiche client ensuite ne la met pas à jour. Voir Émettre une facture et Clients et fournisseurs.
401 Unauthorized
Token absent, expiré ou invalide ; corps vide. Redemandez un token (Authentification).