# Lire un message depuis une suite de tests · Facteur

> Une seule requête suffit : elle attend le message, et vous le rend entièrement analysé.
> https://facteur.eu/fr/aide/lire-un-message

Rechercher dans l'aide Ctrl K   Rechercher dans l'aide  



Tapez au moins deux caractères.

  

 

 #  Lire un message depuis une suite de tests 

 Une seule requête suffit : elle attend le message, et vous le rend entièrement analysé.

 Mis à jour le 17 octobre 2026 

Trois routes suffisent à écrire un test.

```
GET  /messages                   celui que vous cherchez, et il attend qu'il arrive
GET  /inboxes/<id>/messages      les messages d'une inbox, du plus récent au plus ancien
GET  /messages/<id>              un message, en entier

```

Utilisez la première. C'est celle qui attend le message pour vous, et celle qui vous le rend entièrement analysé.

## Avant de commencer

Créez une clé d'API depuis [**Paramètres → Clés d'API**](https://app.facteur.eu/settings/api-keys)et envoyez-la dans un en-tête :

```
Authorization: Bearer fk_votre_cle

```

Il faut la permission `ws:read`. Voir [Clés d'API](/fr/aide/cles-api) pour ce qu'une clé atteint et comment la remplacer.

## Attendre le message plutôt que le sonder

Ajoutez `wait`, en millisecondes, et la recherche ne répond pas tant que rien ne correspond :

```bash
curl <span class="hl-generic">-G</span> https://api.facteur.eu/v1/messages \
  <span class="hl-generic">-H</span> <span class="hl-value">"Authorization: Bearer fk_votre_cle"</span> \
  <span class="hl-generic">--data-urlencode</span> <span class="hl-value">"inbox=j3k9x2mq"</span> \
  <span class="hl-generic">--data-urlencode</span> <span class="hl-value">"sentTo=commande-4821"</span> \
  <span class="hl-generic">--data-urlencode</span> <span class="hl-value">"wait=30000"</span>

```

C'est ce qui remplace la boucle `sleep` ou `retry` que vous écririez autrement. **Lancez la recherche avant de déclencher l'action qui envoie l'email** : elle attendra.

Six choses à savoir :

- **Elle répond dès l'arrivée du message**, pas à l'échéance : la recherche est rejouée toutes les 250 ms.
- **Le message rendu est entièrement analysé** : sujet, code à usage unique, liens.
- **Rien trouvé à l'échéance donne un `200` avec une liste vide**, pas une erreur. C'est à votre test de décider si un message absent est un échec.
- **Cinq minutes au plafond.** Demandez plus, la valeur est ramenée au plafond plutôt que refusée. Sans `wait`, ou avec `wait: 0`, la recherche regarde une fois et répond immédiatement.
- **Vingt attentes simultanées par organisation.** Au-delà, `429 TOO_MANY_WAITS` : en général le signe d'une suite qui lance des recherches sans attendre leurs réponses.
- **`503 API_DRAINING` veut dire « relancez », pas « rien n'est arrivé ».** Quand nous remplaçons une instance, celle qui tenait votre attente vous la rend au lieu de couper la connexion. Relancez la même recherche : une autre instance la reprend. L'en-tête `Retry-After` dit combien attendre.

### Si vous lisez la liste d'une inbox

`GET /inboxes/<id>/messages` n'a pas d'attente. Il vous faut alors une boucle de guet :

```js
<span class="hl-keyword">async</span> <span class="hl-keyword">function</span> <span class="hl-property">attendre</span>(inboxId, versQui, delaiMs = 15000) {
  <span class="hl-keyword">const</span> fin = <span class="hl-type">Date</span>.<span class="hl-property">now</span>() + delaiMs;
  <span class="hl-keyword">while</span> (<span class="hl-type">Date</span>.<span class="hl-property">now</span>() < fin) {
    <span class="hl-keyword">const</span> { messages } = <span class="hl-keyword">await</span> <span class="hl-property">api</span>(`/inboxes/${inboxId}/messages`);
    <span class="hl-keyword">const</span> trouve = messages.<span class="hl-property">find</span>((m) => m.<span class="hl-property">toAddress</span> === versQui && m.<span class="hl-property">status</span> === <span class="hl-value">'parsed'</span>);
    <span class="hl-keyword">if</span> (trouve) <span class="hl-keyword">return</span> trouve;
    <span class="hl-keyword">await</span> <span class="hl-keyword">new</span> <span class="hl-type">Promise</span>((r) => <span class="hl-property">setTimeout</span>(r, 500));
  }
  <span class="hl-keyword">throw</span> <span class="hl-keyword">new</span> <span class="hl-type">Error</span>(`aucun message pour ${versQui} en ${delaiMs} ms`);
}

```

Deux détails séparent un test fiable d'un test capricieux :

1. **Filtrez sur `toAddress`**, pas sur le sujet ni sur « le dernier message ». C'est à cela que sert le sous-adressage, voir [Adresses d'inbox](/fr/aide/adresses-inbox).
2. **Exigez `status === 'parsed'`**. Sinon le test lit parfois un message dont le code n'est pas encore extrait, et échoue une fois sur cinquante.

## Lire le champ `status` avant tous les autres

`status`Ce que ça veut direCe que vous faites`parsed`Tout ce qui dérive du corps est renseignéLisez le message`received`Accepté, **pas encore analysé**Rappelez la route, ou utilisez `wait` qui ne rend que du `parsed` `quota_exceeded`Refusé : une limite de votre plan ou de l'inbox est atteinte. Rien n'a été analyséVoir [Quotas et rétention](/fr/aide/quotas-et-retention)L'analyse a lieu **après** l'acquittement SMTP. Pendant quelques millisecondes, un message existe donc sans sujet, sans code et sans liens. Sans ce champ, « il n'y a pas de code dans ce message » et « le code n'est pas encore extrait » se ressemblent trait pour trait.

Un message refusé **omet les champs extraits** au lieu de les renvoyer vides : `message.otp` vaut `undefined` et non un `null` sur lequel vous écririez une assertion qui passe pour la mauvaise raison. Il porte à la place un `errorCode` qui dit laquelle des limites l'a refusé.

## La forme d'un message

```json
<span class="hl-property">{</span>
  <span class="hl-keyword">"id"</span>: <span class="hl-value">"…"</span>,
  <span class="hl-keyword">"inbox"</span>: <span class="hl-value">"ab12cd34"</span>,
  <span class="hl-keyword">"receivedAt"</span>: <span class="hl-value">"2026-08-07T09:14:02.481Z"</span>,
  <span class="hl-keyword">"deliveryMs"</span>: 412,
  <span class="hl-keyword">"status"</span>: <span class="hl-value">"parsed"</span>,
  <span class="hl-keyword">"fromAddress"</span>: <span class="hl-value">"no-reply@exemple.test"</span>,
  <span class="hl-keyword">"fromName"</span>: <span class="hl-value">"Exemple"</span>,
  <span class="hl-keyword">"toAddress"</span>: <span class="hl-value">"signup+7c1e@ab12cd34.inbox.facteur.eu"</span>,
  <span class="hl-keyword">"subject"</span>: <span class="hl-value">"Votre code de confirmation"</span>,
  <span class="hl-keyword">"textBody"</span>: <span class="hl-value">"…"</span>,
  <span class="hl-keyword">"htmlBody"</span>: <span class="hl-value">"…"</span>,
  <span class="hl-keyword">"otp"</span>: <span class="hl-value">"418302"</span>,
  <span class="hl-keyword">"links"</span>: <span class="hl-property">[</span><span class="hl-value">"https://exemple.test/confirm?token=…"</span><span class="hl-property">]</span>,
  <span class="hl-keyword">"codes"</span>: <span class="hl-property">[</span><span class="hl-property">]</span>,
  <span class="hl-keyword">"headers"</span>: <span class="hl-property">{</span> <span class="hl-keyword">"message-id"</span>: <span class="hl-value">"…"</span>, <span class="hl-keyword">"x-votre-en-tete"</span>: <span class="hl-value">"…"</span> <span class="hl-property">}</span>,
  <span class="hl-keyword">"extract"</span>: <span class="hl-property">{</span><span class="hl-property">}</span>,
  <span class="hl-keyword">"attachments"</span>: <span class="hl-property">[</span>
    <span class="hl-property">{</span>
      <span class="hl-keyword">"id"</span>: <span class="hl-value">"…"</span>,
      <span class="hl-keyword">"fileName"</span>: <span class="hl-value">"facture-4821.pdf"</span>,
      <span class="hl-keyword">"contentType"</span>: <span class="hl-value">"application/pdf"</span>,
      <span class="hl-keyword">"sizeBytes"</span>: 86412,
      <span class="hl-keyword">"checksum"</span>: <span class="hl-value">"sha256:9f2c…"</span>,
      <span class="hl-keyword">"stored"</span>: true
    <span class="hl-property">}</span>
  <span class="hl-property">]</span>,
  <span class="hl-keyword">"inlines"</span>: <span class="hl-property">[</span><span class="hl-property">]</span>
<span class="hl-property">}</span>

```

`deliveryMs` est le temps que le serveur de réception a mis à accepter le message. `headers`, `textBody` et `htmlBody` ne sont que dans la lecture d'un seul message, pas dans les listes. `extract` porte ce que les extracteurs de l'inbox ont trouvé, voir [Pointeurs](/fr/aide/pointeurs).

## Chercher par critères

```bash
curl <span class="hl-generic">-G</span> https://api.facteur.eu/v1/messages \
  <span class="hl-generic">-H</span> <span class="hl-value">"Authorization: Bearer fk_votre_cle"</span> \
  <span class="hl-generic">--data-urlencode</span> <span class="hl-value">"inbox=j3k9x2mq"</span> \
  <span class="hl-generic">--data-urlencode</span> <span class="hl-value">"sentTo=commande-4821"</span> \
  <span class="hl-generic">--data-urlencode</span> <span class="hl-value">"subject=confirmation"</span>

```

Chaque critère est un paramètre de l'adresse (`-G` fait mettre les paramètres dans l'adresse par `curl`, et `--data-urlencode` écrit les caractères spéciaux à votre place). Les mots que vous cherchez font donc partie de l'adresse de la requête, et peuvent être écrits dans les journaux d'un proxy ou d'une passerelle entre votre machine et Facteur. Facteur, lui, ne les écrit ni dans ses journaux d'accès, ni dans ses journaux d'application, ni dans ses traces, et il n'écrit pas non plus l'adresse de la page de l'application d'où une recherche est lancée (le navigateur n'indique que le nom du site). Les journaux d'un composant qui n'est pas à Facteur sont hors de sa portée. Cherchez une adresse, un sujet ou un code, jamais un secret.

**Les critères.** `sentTo`, `sentFrom`, `subject`, `body`, `otp`, `attachmentName`. Ce sont des sous-chaînes, insensibles à la casse. `%` n'est pas un joker et ne cherche que le caractère `%`. `otp` cherche le code extrait et lui seul : `body=882314`trouverait aussi un message qui ne fait que citer ce nombre.

**Les bornes.** `since`, `hasAttachment` et `hasOtp` rétrécissent la fenêtre au lieu de s'ajouter aux alternatives.

**`match`.** `all` par défaut : tous les critères doivent correspondre. Passez `any`pour qu'un seul suffise. Une borne n'entre jamais dans le `any`, sinon « depuis hier ou de Camille » vous rendrait des messages d'avant-hier.

**Les longueurs maximales.** En caractères : `inbox` 36, `workspace` 40, `sentTo` et `sentFrom` 254, `subject`, `body` et `attachmentName` 255, `otp` 16. Une valeur plus longue est refusée avec `400 CRITERIA_INVALID` plutôt que coupée : coupée, elle trouverait d'autres messages. Un `cursor` fait 64 caractères au plus, et un plus long répond `400 INVALID_CURSOR`. Un critère vrai ou faux (`hasOtp`, `hasAttachment`) s'écrit `true` ou `false`, et `limit` et `wait` sont des nombres.

**Où chercher.** `inbox` pour une inbox, `workspace` (l'adresse de l'espace) pour un espace entier, ou ni l'un ni l'autre pour tout ce que votre clé atteint.

**La pagination.** `limit` (50 par défaut, 200 au plus) et le `nextCursor` que la réponse renvoie, à repasser dans `cursor`. Reprenez-le tel quel ; il est nul sur la dernière page, ce qui évite de comparer un compte à une limite.

**Le transitoire.** Par défaut, la recherche ne rend que les messages `parsed`. Passez `status=any` pour voir aussi ceux qui viennent d'arriver et ceux qui ont été refusés.

### Deux refus qui vous font gagner une heure

- **Un critère inconnu est refusé**, pas ignoré. `subjet` au lieu de `subject` répond `400 CRITERIA_UNKNOWN` en nommant le champ. Ignoré, il vous aurait rendu *tous* les messages de l'inbox, et votre test serait passé pour la mauvaise raison jusqu'au jour où deux messages arrivent.
- **Une inbox qui n'est pas la vôtre répond `404`**, pas une liste vide. Une liste vide se lit « le message n'est pas arrivé » et envoie déboguer un pipeline qui fonctionne.

## Pièces jointes

`attachments` liste les vraies pièces jointes. `inlines` liste les images référencées par le corps HTML (`cid:`), le plus souvent un logo de signature. Les deux sont séparées pour que `attachments.length` ne change pas selon que l'expéditeur a ajouté une bannière à sa signature.

**Vérifier le contenu sans télécharger le fichier.** Comparez le `checksum`, qui porte l'empreinte SHA-256 du fichier reçu préfixée par `sha256:`, à celle de votre fichier local :

```bash
sha256sum facture-<span class="hl-number">4821</span>.pdf
<span class="hl-comment"># 9f2c…  facture-4821.pdf</span>

```

**Télécharger les octets** :

```bash
curl <span class="hl-generic">-H</span> <span class="hl-value">"Authorization: Bearer fk_votre_cle"</span> \
  https://api.facteur.eu/v1/attachments/<span class="hl-operator"><</span>id<span class="hl-operator">></span> <span class="hl-generic">-o</span> facture.pdf

```

Les mêmes règles d'accès que pour le reste : une clé qui n'atteint pas l'espace de travail contenant le message reçoit `404 ATTACHMENT_NOT_FOUND`, qu'une pièce jointe existe réellement sous cet identifiant ou non.

**`stored` peut valoir `false`.** Le fichier est alors décrit (nom, taille, empreinte) mais ses octets ne sont pas conservés, et `GET /attachments/<id>` répond `404 ATTACHMENT_NOT_STORED`. Le champ `reason` dit lequel des quatre cas :

`reason`Ce que ça veut direCe que vous faites`PERFORMANCE_MODE`L'inbox est réglée pour ne rien stockerChangez le réglage de l'inbox si vous voulez les octets`TRIAL_INBOX`L'inbox est une inbox d'essai, qui ne garde pas les piècesUtilisez une inbox de votre organisation`ATTACHMENT_TOO_LARGE`Le fichier dépasse la taille permise par votre planChangez de plan, ou testez avec un fichier plus petit`STORAGE_QUOTA_EXCEEDED`Le stockage de l'organisation est pleinLibérez de la place ou achetez un pack de stockage## Limites connues

- **Pas de recherche plein texte.** Les critères sont des sous-chaînes. Chercher un mot avec une casse ou un accent différent ne le trouvera pas.

 

  Cet article ne répond pas à votre question ? Écrivez à l'équipe : elle saura de quel article vous venez.

 [ Écrire au support ](https://app.facteur.eu/support?article=lire-un-message)
