REST API
Traduit automatiquement de l’anglais.
Lire en anglaisURL de base https://api.proxymail.qa/v1, avec Authorization: Bearer qa_…. Chaque endpoint figure dans la référence REST API, générée à partir de la description OpenAPI.
Vous utilisez JavaScript ou TypeScript ? Le paquet proxymail-qa sur npm encapsule ces appels, avec des types.
Démarrage rapide
Section intitulée « Démarrage rapide »Obtenir une clé d’API
Section intitulée « Obtenir une clé d’API »Utilisez une clé d’API du projet que vous testez.
Créer une adresse
Section intitulée « Créer une adresse »Créez une adresse avec un persona et un mot de passe, et inscrivez-vous avec dans votre application.
curl -X POST https://api.proxymail.qa/v1/addresses \-H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" -H "Content-Type: application/json" \-d '{"label": "Sign-up test", "persona": true, "password": true}'Attendre le code
Section intitulée « Attendre le code »Attendez ensuite le code. Il arrive tout seul.
curl "https://api.proxymail.qa/v1/addresses/k7f2x9ab12cd@mailclub.dev/code" \-H "Authorization: Bearer $PROXYMAIL_QA_API_KEY"{"type": "code","subtype": "otp","value": "986512","service": "Spotify","received_at": "2026-10-12T10:00:03Z","expires_at": "2026-10-12T10:10:03Z"}
Endpoints
Section intitulée « Endpoints »| Méthode et chemin | Ce qu’il fait |
|---|---|
GET /v1/project |
Le projet de la clé, ses domaines de messagerie et ses limites |
PATCH /v1/project |
Modifier name, website, environments, email_prefix ou mail_domain du projet, comme dans Paramètres du projet. Nécessite le responsable du projet, ou un propriétaire ou administrateur de l’organisation ; chaque modification figure dans le journal d’activité |
POST /v1/project/dev-hosts |
Marquer un hôte tel que localhost:3000 comme environnement de développement (host), pour que les vérifications d’e-mail acceptent les liens vers lui |
POST /v1/addresses |
Créer une adresse (label, name, persona, password, lifetime_days, domain, age ou date_of_birth, country, et où elle est utilisée : site et environment) |
POST /v1/form-values |
Ce qu’il faut saisir ou choisir dans chaque champ d’un formulaire d’inscription, à partir d’une nouvelle adresse créée pour lui ou d’une adresse créée plus tôt (en savoir plus) |
GET /v1/addresses |
Lister les adresses, de la plus récente à la plus ancienne, ou retrouver celles créées plus tôt (tag, country, min_age, max_age, has_password, search, site, environment) |
GET / PATCH / DELETE /v1/addresses/{address} |
Lire, modifier (first_name, last_name, username, date_of_birth, country, label, notes, tags) ou supprimer une adresse |
GET /v1/addresses/{address}/code |
Attendre un code (timeout, service, since ; unused=true ignore les codes déjà utilisés ; type=coupon pour un coupon ou un code promo, avec sa remise et sa date de validité) |
GET /v1/addresses/{address}/link |
Attendre un lien (type : verify, magic_signin, invite, reset ou any ; unsubscribe ou list_unsubscribe pour un lien de désabonnement ; app=true pour un lien qui ouvre une application mobile) |
GET /v1/addresses/{address}/count |
Compter les e-mails arrivés (since, service, type : code ou lien, subtype, category), chacun résumé sans son contenu |
GET /v1/addresses/{address}/none |
Vérifier qu’aucun e-mail n’arrive : attend jusqu’à 30 secondes (timeout), 200 si rien n’est arrivé, 409 dès qu’un e-mail arrive ; category=marketing ne surveille que les offres et les newsletters, et until garde une fenêtre ouverte jusqu’à une heure entre les appels |
GET, PUT /v1/addresses/{address}/consent |
Labs. Ce que l’adresse a accepté à l’inscription (marketing, case précochée, double opt-in), pour les règles marketing par pays |
POST /v1/addresses/{address}/coupon-result |
Labs. Ce que le paiement a fait d’un coupon (accepted, rejected ou error, avec le message de l’application), et le verdict coupon_result |
POST /v1/addresses/{address}/sequence |
Labs. Une série de bienvenue ou une campagne goutte-à-goutte : si chaque e-mail est arrivé dans l’ordre et à temps après since |
GET /v1/addresses/{address}/offers |
Labs. Les coupons de chaque expéditeur et la variante de l’adresse (simple, majuscules, +tag) à laquelle chacun a été envoyé : reused quand une personne a reçu une offre deux fois |
GET, POST, DELETE /v1/addresses/{address}/unsubscribed |
Labs. Surveillance des désabonnements : POST enregistre que l’adresse s’est désabonnée d’un expéditeur, DELETE qu’elle s’est réabonnée, et GET liste les e-mails marketing arrivés ensuite |
GET /v1/addresses/{address}/checks |
Vérifier un e-mail : SPF, DKIM et DMARC, liens locaux et http simples, destination de son lien (expect_host), expiration indiquée, fin d’un coupon, partie texte brut, taille, e-mails composés uniquement d’images, phrases données (expect_text), langue (expect_language), délai d’arrivée (expect_within) et pièces jointes (expect_attachment). Verdicts uniquement |
GET /v1/addresses/{address}/messages |
Les e-mails avec leurs codes et liens ; include=text ajoute le texte, include=html le HTML, include=text,html les deux |
GET /v1/addresses/{address}/variants |
Variantes inhabituelles de l’adresse pour tester les formulaires d’inscription : celles qui atteignent sa boîte de réception, celles qui ne le peuvent pas, et les invalides (en savoir plus) |
GET /v1/variants/names |
Noms difficiles pour le champ nom d’un formulaire (field, max_length) : à accepter, à refuser, du HTML ou du SQL à stocker comme texte, et ceux sur lesquels les applications diffèrent (en savoir plus) |
GET /v1/variants/passwords |
Mots de passe difficiles respectant les règles du champ (min_length, max_length, symbols) : le minimum et le maximum, chaque classe de symboles, les espaces, le non-ASCII, et ceux sur lesquels les applications diffèrent (en savoir plus) |
GET /v1/addresses/{address}/delivery |
Vérifier une adresse : ce qui lui est parvenu, ce que nous avons refusé, ce que les serveurs de messagerie de Cloudflare ont rejeté et pourquoi, et une piste |
GET /v1/addresses/{address}/password |
Un mot de passe enregistré, pour les clés autorisées à les lire |
POST /v1/addresses/sign-in |
Le compte de test enregistré avec lequel se connecter et son mot de passe, en un seul appel (site, environment, tags), pour les clés autorisées à lire les mots de passe (en savoir plus) |
PUT /v1/addresses/{address}/password |
Enregistrer le mot de passe actuel du compte de test (password, ou l’omettre pour en générer un, renvoyé une seule fois) ; toute clé peut en enregistrer un |
POST /v1/addresses/checkout |
Emprunter un compte du pool : un compte correspondant aux filtres (tags, country, min_age, max_age, has_password, search, site, environment) que personne d’autre ne détient, pour minutes (en savoir plus) |
POST /v1/addresses/{address}/extend, POST /v1/addresses/{address}/return |
Le garder plus longtemps, ou le rendre (token ; au retour, add_tags et remove_tags enregistrent son état) |
POST /v1/runs |
Démarrer une exécution (name, source, meta) : les appels qui envoient son id dans l’en-tête Proxy-Mail-QA-Run ajoutent ce qu’ils ont vu (en savoir plus) |
GET /v1/runs, GET /v1/runs/{id} |
Lister les exécutions, ou en lire une avec ses preuves ; format=markdown donne le rapport |
POST /v1/runs/{id}/notes, POST /v1/runs/{id}/tests, POST /v1/runs/{id}/finish |
Ajouter une ligne à vous (text, failed), un résultat de test ou les totaux, ou la terminer (failed) |
Les endpoints marqués Labs font partie des e-mails marketing et promotionnels, une fonctionnalité Labs que le créateur de la clé active dans l’app sous Account settings → Labs. D’ici là, ils répondent 403 forbidden avec un message qui l’indique.
Coupons et offres
Section intitulée « Coupons et offres »Après l’inscription à une offre, attendez son coupon avec type=coupon (any pour un coupon ou un code de connexion). Les offres prennent souvent plusieurs minutes : attendez plus longtemps ou redemandez ; avec since, after_seconds indique combien de temps après l’e-mail est arrivé :
curl "https://api.proxymail.qa/v1/addresses/$ADDRESS/code?type=coupon&since=$SIGNED_UP&timeout=30" -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY"# { "value": "WELCOME20", "subtype": "coupon", "discount": "20% off", "valid_until": "2026-10-31", "after_seconds": 212, … }discount est notre lecture de l’offre (20% off, $10 off, Free shipping, Buy one, get one), et valid_until la date de fin indiquée dans l’e-mail (« valable jusqu’au 31 octobre », « se termine le 31 oct. », « valable 7 jours »). Chacun vaut null quand l’e-mail ne le précise pas. Les attentes sans type ignorent les coupons, pour qu’un test de connexion ne récupère jamais le code d’une offre. Pour vérifier qu’il est arrivé à temps, GET /checks?since=$SIGNED_UP&expect_within=5m fait échouer arrival_time s’il est arrivé plus tard.
Une offre par personne. Les e-mails envoyés à name+promo1@… et à NAME@… arrivent dans la même boîte de réception : ils font de bons « deuxièmes clients » pour une offre unique ou de bienvenue. Inscrivez-vous avec l’adresse simple et avec des variantes issues de GET /variants, attendez chaque coupon, puis GET /v1/addresses/{address}/offers : pour chaque expéditeur, ses coupons et la variante à laquelle chacun a été envoyé (plain, capitals ou +tag), reused quand au moins deux variantes en ont reçu un, et same_code quand il s’agissait d’un seul code partagé. passed vaut false si une offre a été réutilisée, et chaque coupon après le premier fait échouer offer_reuse dans /checks. L’astuce du point de Gmail ne peut pas être testée ici : sur ces domaines, un point crée une autre adresse.
Au paiement. Appliquez le coupon avec vos propres outils, uniquement en préproduction avec des paiements de test, puis enregistrez ce qu’a fait l’application :
curl -X POST "https://api.proxymail.qa/v1/addresses/$ADDRESS/coupon-result" \ -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" -H "Content-Type: application/json" \ -d '{"result": "rejected", "code": "WELCOME20", "message": "Code already used", "one_time": true}'result vaut accepted, rejected ou error ; code prend par défaut le coupon le plus récent de l’adresse. Chaque tentative est conservée, et le check de la réponse est le verdict coupon_result : il échoue pour un coupon accepté après sa date de fin, accepté pour une offre déjà donnée à une autre variante de l’adresse, ou accepté deux fois avec one_time, et avertit en cas d’erreur ou de refus sans motif. Avec une exécution, un verdict en échec la fait échouer.
Valeurs pour un formulaire d’inscription
Section intitulée « Valeurs pour un formulaire d’inscription »Quand un formulaire demande plus qu’un e-mail, envoyez ce que la page indique sur chaque champ et récupérez ce qu’il faut saisir ou choisir dans chacun. Les valeurs proviennent d’une nouvelle adresse créée pour le formulaire : elle reçoit une date de naissance si le formulaire en demande une (adulte, sauf si vous passez age), un pays proposé par la liste déroulante du formulaire (ou country), et un mot de passe conforme aux min_length, max_length et pattern du champ mot de passe, enregistré sur l’adresse et affiché uniquement dans cette réponse. Passez address pour utiliser plutôt une adresse créée plus tôt (son mot de passe enregistré n’est pas renvoyé).
curl -s -X POST https://api.proxymail.qa/v1/form-values -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" \ -H "Content-Type: application/json" -d '{ "fields": [ { "id": "email", "type": "email", "label": "Email" }, { "id": "user", "name": "username", "label": "Username", "max_length": 15 }, { "id": "pw", "type": "password", "label": "Password", "min_length": 12 }, { "id": "dob", "type": "text", "label": "Date of birth", "placeholder": "DD/MM/YYYY" }, { "id": "country", "type": "select", "label": "Country", "options": [{ "value": "de", "label": "Germany" }, { "value": "uk", "label": "United Kingdom" }] }, { "id": "terms", "type": "checkbox", "label": "I agree to the terms" } ] }'# { "address": "k7f2x9ab12cd@mailclub.dev", "created": true, "password": "…", "persona": {…},# "values": [ { "id": "email", "kind": "email", "value": "k7f2x9ab12cd@mailclub.dev" },# { "id": "dob", "kind": "date_of_birth", "value": "04/05/1990" },# { "id": "country", "kind": "country", "value": "uk" },# { "id": "terms", "kind": "other", "value": null, "note": "Not a sign-up detail Proxy Mail QA fills: fill it yourself, or leave it." }, … ] }Chaque champ peut avoir un id (le vôtre, renvoyé avec sa valeur), type (ou select), name, label, placeholder, autocomplete, group (ce qu’indique le groupe qui l’entoure, comme « Date de naissance » au-dessus de listes déroulantes jour, mois et année), pattern, min_length, max_length et options (value et label). Les champs jour, mois et année reçoivent chacun leur partie, un sélecteur de date reçoit YYYY-MM-DD, et une date saisie reçoit le format indiqué par son libellé ou son placeholder (ordre américain avec un note si rien n’est indiqué). Proxy Mail QA ne voit ni ne remplit jamais la page : c’est vous qui le faites, puis vous attendez le code ou le lien comme d’habitude.
Variantes d’une adresse
Section intitulée « Variantes d’une adresse »Partout où l’API accepte une adresse, une variante fonctionne aussi : des majuscules, ou un tag plus comme k7f2x9ab12cd+o'brien@mailclub.dev (encodé dans l’URL du chemin). Elle agit sur l’adresse simple, et une attente ou une liste de messages pour une variante +tag ne trouve que les e-mails envoyés à cette variante. Voir Adresses e-mail inhabituelles.
Noms et mots de passe
Section intitulée « Noms et mots de passe »Les autres champs du formulaire sont traités de la même façon. GET /v1/variants/names?field=first_name&max_length=50 liste des noms en quatre groupes : accept (le nom d’une vraie personne : O'Brien, Smith-Jones, José, Straße, 李, Мария, محمد, สมชาย, une seule lettre, exactement le maximum), reject (un de plus que le maximum, vide, uniquement des espaces), store_as_text (<b>Jane</b>, une balise script, une injection SQL, {{7*7}} : l’application doit les stocker et les afficher exactement tels que saisis, sans jamais les exécuter) et decide (chiffres, symboles, un emoji, des espaces autour, des accents composés et décomposés, et 64, 128 et 255 caractères quand aucun maximum n’est donné : les applications diffèrent, notez donc ce qu’a fait l’application). GET /v1/variants/passwords?min_length=8&max_length=64&symbols=true fait de même pour un mot de passe : accept (exactement le minimum et le maximum, chaque classe de symboles y compris les guillemets, une barre oblique inverse et un espace, des lettres non ASCII), reject et decide (uniquement des chiffres, uniquement des minuscules, un espace au début ou à la fin, un emoji, et une paire partageant leurs 72 premiers octets quand le maximum le permet, là où bcrypt s’arrête). Les mots de passe sont nouveaux à chaque appel. Chaque entrée a tests (ce qu’elle vérifie) et value (ce qu’il faut saisir). Voir Noms et mots de passe inhabituels.
Dates de naissance
Section intitulée « Dates de naissance »Ajoutez "age": "under13" (ou adult, over21, under18, under15, any, turns18tomorrow, turned18today) pour donner au persona une date de naissance dans cette tranche, ou envoyez la vôtre avec "date_of_birth": "2012-05-01". Elle est renvoyée en persona.date_of_birth, au format AAAA-MM-JJ, et uniquement si l’adresse en a une.
Retrouver un compte de test
Section intitulée « Retrouver un compte de test »GET /v1/addresses accepte des filtres, pour qu’un test ou un agent puisse revenir à un compte créé plus tôt au lieu d’en créer un autre. Chaque filtre donné doit correspondre :
tag: répétez-le, ou séparez les tags par des virgules, pour les exiger touscountry: le pays du persona, par exempleGBmin_ageetmax_age: l’âge actuel en années révolues, d’après la date de naissance du persona (max_age=17trouve les moins de 18 ans) ; les adresses sans date de naissance ne correspondent pashas_password:trueoufalse, selon qu’un mot de passe y est enregistrésearch: une partie de l’adresse, du libellé, des notes, du nom ou du nom d’utilisateursiteetenvironment: où elle est utilisée, sous forme d’hôte ou d’URL de page (site=staging.example.com) et de nom (environment=staging). Définissez-les à la création d’une adresse ; l’extension Chrome les enregistre aussi, et l’application les affiche sur chaque compte
curl "https://api.proxymail.qa/v1/addresses?tag=age-gate&country=GB&max_age=17&has_password=true" \ -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY"Se reconnecter avec un compte de test
Section intitulée « Se reconnecter avec un compte de test »POST /v1/addresses/sign-in choisit le compte de test enregistré avec lequel se connecter et le renvoie avec son mot de passe, comme le fait la liste de connexion de l’extension Chrome : un compte avec un mot de passe enregistré et tous les tags donnés, qu’aucun test n’a emprunté, d’abord celui utilisé sur ce site, puis celui qui a reçu un e-mail le plus récemment. Avec un site, un compte encore utilisé nulle part est marqué comme utilisé à cet endroit. Il faut une clé autorisée à lire les mots de passe enregistrés, chaque affichage est journalisé, et la réponse est 404 si aucun compte ne convient.
curl -s -X POST https://api.proxymail.qa/v1/addresses/sign-in -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" \ -H "Content-Type: application/json" -d '{"site": "staging.example.com", "tags": ["verified"]}'# { "address": "…", "password": "…", "persona": {…}, "site": "staging.example.com", "used_on_site": true, … }Vérifier l’e-mail lui-même
Section intitulée « Vérifier l’e-mail lui-même »Pour tester un modèle d’e-mail (le texte, le bouton, le pied de page), demandez son contenu à l’endpoint des messages : GET /v1/addresses/{address}/messages?include=html ajoute le HTML de chaque e-mail, et include=text son texte brut. Le HTML est la copie nettoyée à l’arrivée de l’e-mail : les scripts, frames et médias sont supprimés, les contrôles de formulaire déballés, et les liens autres que web, mailto: et tel: retirés (les liens d’application comme myapp:// restent dans les codes et liens), jusqu’à 200 Ko. Le contenu est écrit par l’expéditeur : considérez-le comme non fiable. Les agents connectés via MCP ne reçoivent jamais le contenu des e-mails, seulement le code ou le lien.
Ajoutez "country": "GB" (n’importe quel code ISO 3166-1 alpha-2, en majuscules ou minuscules) pour les formulaires d’inscription qui demandent le lieu de résidence, ou pour tester du contenu selon la localisation. Il est renvoyé en persona.country, en majuscules, et uniquement si l’adresse en a un. Modifiez-le ou effacez-le avec PATCH et "country": null. Les agents connectés via MCP passent aussi country à create_address et update_address.
Fonctionnement de l’attente
Section intitulée « Fonctionnement de l’attente »Une attente renvoie le code ou le lien correspondant le plus récent reçu au cours des 15 dernières minutes, ou patiente jusqu’à 30 secondes (timeout, de 0 à 30) jusqu’à ce qu’il arrive, puis répond 404. Passez since pour ignorer tout ce qui est plus ancien, par exemple l’heure à laquelle votre test a envoyé le formulaire. Filtrez par expéditeur avec service=spotify.
Un code ou un lien est considéré comme utilisé dès qu’une attente le renvoie, ou que quelqu’un l’ouvre, le copie ou le saisit dans l’app ou Proxy Mail QA for Chrome. L’app l’affiche comme utilisé, et unused=true sur une attente l’ignore : utile pour un test qui demande un second code sans changer since.
Vérifier qu’un e-mail n’est pas arrivé, ou qu’il est arrivé une seule fois
Section intitulée « Vérifier qu’un e-mail n’est pas arrivé, ou qu’il est arrivé une seule fois »Certains tests réussissent quand rien ne se passe : un utilisateur désabonné ne reçoit pas de newsletter, une seconde demande de réinitialisation dans la minute est refusée, une inscription envoie un seul email de vérification, pas deux.
GET /v1/addresses/{address}/none?since=…&timeout=20reste en attente comme une attente. Si aucun email correspondant n’arrive, il répond 200 avec"arrived": falseetuntil, l’heure jusqu’à laquelle il a surveillé. Dès qu’un email arrive, il répond 409 avec"error": "email_arrived", lecount, ainsi que l’heure, le service et le type du premier email, pour quecurl --failfasse échouer l’étape. Passezsinceà l’heure de l’action, pour qu’un email arrivé avant l’appel compte quand même ; sans cela, la surveillance commence à la requête. Pour surveiller plus longtemps, ajoutezuntil(jusqu’à 60 minutes aprèssince) et rappelez avec les mêmessinceetuntiltant que la réponse indique"done": false;seconds_leftindique le temps restant.GET /v1/addresses/{address}/count?since=…répond tout de suite aveccountet les 50 premiers emails, du plus ancien au plus récent. Pour « exactement un », comptez une fois que l’attente du premier email a répondu et qu’un doublon a eu le temps d’arriver.
Les deux filtrent comme une attente : service, et type=code (codes autres que les coupons) ou type=link (liens de vérification, de connexion magique, d’invitation et de réinitialisation), affinés avec subtype, par exemple type=link&subtype=reset, et par category. Pour « un utilisateur désabonné ne reçoit pas de newsletter », utilisez category=marketing, pour qu’un reçu ou une réinitialisation de mot de passe qui doit toujours arriver ne le fasse pas échouer. Une variante +tag ne compte que ses propres emails. Aucun des deux ne renvoie le contenu des emails : chacun est résumé par received_at, service ainsi que le type et le subtype de son code ou lien principal. Le client Node.js propose countEmails et expectNoEmail, et l’assistant Python count_emails et expect_no_email.
Types d’email, liens de désabonnement et pièces jointes
Section intitulée « Types d’email, liens de désabonnement et pièces jointes »Chaque email reçu se voit attribuer un type, avec ses raisons :
category |
Quand |
|---|---|
auth |
Son code ou lien principal connecte, vérifie, réinitialise un mot de passe ou invite, ou son objet l’indique |
transactional |
Son objet ressemble à un reçu, une commande, un paiement, une réservation ou un avis de sécurité, ou une facture PDF ou une invitation d’agenda est jointe ; et tout ce qui n’est ni connexion ni marketing |
marketing |
Il a des en-têtes List-Unsubscribe, List-Unsubscribe-Post ou List-Id, Precedence: bulk, un coupon, un objet promotionnel ou un lien de désabonnement |
Les reçus passent avant le marketing exprès : un reçu avec un code « 10 % de réduction la prochaine fois » reste un reçu. /count, /none et /checks acceptent category, et /count donne pour chaque email son category, son gap_seconds (depuis le précédent) et les types, extensions et tailles de ses pièces jointes. Les emails arrivés avant l’ajout des types n’en ont pas.
Les liens de désabonnement d’un email sont conservés comme valeurs : GET /link?type=unsubscribe donne son propre lien de désabonnement ou de préférences d’email, et type=list_unsubscribe celui de l’en-tête List-Unsubscribe, celui qu’utilise le bouton Se désabonner de Gmail. Pour faire comme ce bouton, envoyez List-Unsubscribe=One-Click en POST à ce lien avec vos propres outils. Proxy Mail QA n’ouvre jamais ni l’un ni l’autre.
Les pièces jointes sont conservées sous forme de nom, type et taille de chaque fichier, jamais le fichier lui-même. GET /checks?expect_attachment=.pdf (à répéter pour plusieurs) fait échouer expected_attachment s’il en manque une ; un motif est un type (application/pdf, image/*), une extension (.ics) ou un nom de fichier avec * (invoice-*.pdf). /messages les liste avec leurs noms.
Surveillances de désabonnement
Section intitulée « Surveillances de désabonnement »Pour tester que le désabonnement fonctionne, désabonnez l’adresse avec vos propres outils : ouvrez le lien de désabonnement de l’email (GET /link?type=unsubscribe), ou envoyez List-Unsubscribe=One-Click en POST au lien de l’en-tête (type=list_unsubscribe). Puis enregistrez-le :
curl -X POST "https://api.proxymail.qa/v1/addresses/k7f2x9ab12cd@mailclub.dev/unsubscribed" \ -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" -H "Content-Type: application/json" \ -d '{"email_id": "…", "at": "2026-10-19T10:02:00Z"}'email_id est l’email dont vous vous êtes désabonné ; service (un nom ou un domaine comme acme.com) fonctionne aussi, et sans aucun des deux, c’est l’email marketing le plus récent qui décide. at l’antidate ; marquer deux fois est sans effet. grace (comme 2h ou 1d, jusqu’à 30 jours) laisse passer le marketing déjà en route : un email dans ce délai ne compte pas. Ensuite, tout email marketing de cet expéditeur fait échouer after_unsubscribe dans /checks, quel que soit le délai, et apparaît dans le tableau de bord Rapports. Les reçus, réinitialisations de mot de passe et emails de connexion ne comptent jamais. GET /unsubscribed liste chaque surveillance avec les emails reçus après (after_unsubscribe : identifiants, heures et after_seconds, jamais leur contenu) et passed ; avec un run, une vérification échouée le fait échouer. DELETE /unsubscribed?service=acme.com enregistre que l’adresse s’est réabonnée.
Pour une vérification dans le test, déclenchez une autre campagne et gardez GET /none?category=marketing&since=…&until=… ouvert quelques minutes.
Une série d’emails
Section intitulée « Une série d’emails »Pour une série de bienvenue ou une campagne goutte-à-goutte, POST /v1/addresses/{address}/sequence vérifie que chaque email est arrivé dans l’ordre et à l’heure :
curl -X POST "https://api.proxymail.qa/v1/addresses/$ADDRESS/sequence" \ -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" -H "Content-Type: application/json" \ -d '{"since": "2026-10-01T10:00:00Z", "steps": [{"subject": "Welcome"}, {"subject": "tips", "after": "1d"}, {"category": "marketing", "after": "7d", "within": "12h"}]}'Chaque étape peut indiquer une phrase de son subject, son category, son type (code ou lien), after (quand il doit arriver, à partir de since, jusqu’à 90 jours) et within (l’écart encore accepté ; par défaut un dixième de after, au moins 10 minutes). Chacune prend le premier email correspondant après l’étape précédente, et répond pass, early, late, missing ou waiting (pas encore dû), avec received_at et off_seconds. done vaut false tant qu’une étape attend, alors vérifiez de nouveau plus tard. Les objets sont comparés sur le serveur et jamais renvoyés.
Consentement et règles marketing par pays
Section intitulée « Consentement et règles marketing par pays »Enregistrez ce que le compte a accepté à l’inscription, et /checks juge chaque email marketing selon les règles du lieu de résidence du persona (son country, défini à la création de l’adresse) :
curl -X PUT "https://api.proxymail.qa/v1/addresses/$ADDRESS/consent" \ -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" -H "Content-Type: application/json" \ -d '{"marketing": "declined", "prechecked": false}'marketing vaut opted_in ou declined, prechecked indique que la case était déjà cochée au chargement du formulaire, et double_opt_in vaut confirmed ou pending (il se confirme de lui-même quand le lien de vérification est utilisé). Ne donnez que ce qui a changé ; GET /consent le relit. L’UE et l’EEE, le Royaume-Uni, les États-Unis, le Canada, l’Australie, la Nouvelle-Zélande, la Suisse, le Japon, la Corée du Sud, Singapour et Hong Kong sont couverts, et chaque verdict cite sa source officielle et la date de notre vérification. Consultez les règles des emails marketing par pays pour chaque règle. C’est une aide aux tests, pas un conseil juridique.
Vérifier l’envoi et la construction d’un e-mail
Section intitulée « Vérifier l’envoi et la construction d’un e-mail »GET /v1/addresses/{address}/checks effectue des vérifications sur un email : le plus récent correspondant aux mêmes filtres qu’un comptage (since, service, type, subtype), ou celui désigné par email_id. Il n’attend pas, alors attendez d’abord l’email. Chaque vérification réussit, échoue, avertit ou est ignorée, avec une raison d’une ligne, et passed vaut true quand aucune n’a échoué :
| Vérification | Échoue ou avertit quand |
|---|---|
spf, dkim, dmarc |
Le serveur d’envoi n’est pas autorisé à envoyer pour le domaine, l’email n’est pas signé, ou il ne respecte pas la politique DMARC du domaine From : les boîtes de réception peuvent le classer en spam ou le rejeter. Ce sont les verdicts du serveur de réception lui-même |
local_links |
Un lien mène à localhost ou à une adresse privée, directement ou après une redirection comme le redirect_to de Supabase. Les liens vers un hôte marqué comme environnement de développement (dans les paramètres du projet, ou POST /v1/project/dev-hosts) réussissent |
plain_http |
Un lien sur un site public n’est pas en https |
expected_host |
Avec expect_host=staging.example.com : le lien de connexion, de vérification, de réinitialisation ou d’invitation aboutit ailleurs, par exemple en production. Les liens qui ouvrent une app (myapp://…) n’ont pas de site et ne sont pas comparés. Ignorée sans lui |
expiry |
L’email n’indique pas quand son code ou lien de connexion cesse de fonctionner (un avertissement ; ignorée pour les autres liens, comme ceux d’une newsletter) |
coupon_expiry |
Labs. L’email contient un coupon mais n’indique pas sa date de fin, comme « valable jusqu’au 31 octobre » ou « valable 7 jours » (un avertissement). Chaque coupon compte, pas seulement le code principal. Ignorée sans coupon |
plain_text |
Il n’y a pas de version texte brut (un avertissement) |
size |
Le HTML dépasse 102 Ko, seuil où Gmail le tronque (un avertissement) |
images_only |
Il est presque entièrement composé d’images, donc sans images il ne dit presque rien |
layout |
Il risque de ne pas tenir sur un téléphone : éléments de largeur fixe supérieure à 640 px sans styles mobiles (@media), images sans texte alternatif (vides sans images), ou texte de moins de 11 px. Un avertissement, d’après le HTML seul, sans rendu |
expected_text |
Avec expect_text (à répéter, jusqu’à 10 phrases) : l’objet ou le corps n’en contient pas une, comme le prénom de l’utilisateur ou le nom du produit. Casse, guillemets typographiques et espaces n’ont pas d’importance, et la raison ne cite que les phrases manquantes. Ignorée sans lui |
expected_attachment |
Avec expect_attachment=.pdf (à répéter pour plusieurs) : l’une d’elles n’est pas jointe. Ignorée sans lui, ou pour un email arrivé avant l’enregistrement des pièces jointes |
arrival_time |
Avec since et expect_within=5m (ou 90s, 2h, un nombre de secondes) : il est arrivé plus tard que cela après since, comme un coupon de bienvenue qui a mis 14 minutes. Ignorée sans lui |
list_unsubscribe |
Labs. Email marketing uniquement : il n’y a pas d’en-tête List-Unsubscribe (échec), ou seulement une adresse email ou une adresse http simple (avertissement). Les boîtes de réception affichent alors leur propre bouton Se désabonner, et Gmail et Yahoo l’exigent des expéditeurs en masse |
one_click_unsubscribe |
Labs. Email marketing uniquement : pas d’en-tête List-Unsubscribe-Post: List-Unsubscribe=One-Click avec une adresse https, ou une signature DKIM vérifiée qui ne couvre pas les deux en-têtes List-Unsubscribe (le RFC 8058 l’exige, sinon les boîtes de réception ne proposent pas le désabonnement en un clic), donc le bouton d’une boîte de réception ne peut pas désabonner en une étape (un avertissement) |
unsubscribe_link |
Labs. Email marketing uniquement : l’email lui-même n’a pas de lien de désabonnement ou de préférences d’email |
after_unsubscribe |
Labs. Email marketing uniquement : il est arrivé après le désabonnement de l’adresse auprès de cet expéditeur (voir Surveillances de désabonnement). Ignorée quand l’adresse n’est pas marquée comme désabonnée de lui |
offer_reuse |
Labs. Un coupon d’un expéditeur qui en a déjà envoyé un à une autre variante de l’adresse (name+promo1@…, majuscules) : l’app a traité une personne comme plusieurs. Ignorée pour les emails sans coupon |
coupon_result |
Labs. Ce que le paiement a fait de son coupon, une fois un résultat enregistré : accepté après sa date de fin, pour une offre réutilisée ou deux fois s’il est à usage unique (échec) ; une erreur, ou un refus à la première utilisation dans ses dates (avertissement). Ignorée sans résultat |
marketing_consent |
Labs. Email marketing envoyé à un compte qui l’a refusé, là où un consentement préalable est requis (UE et EEE, Royaume-Uni, Canada, Australie, Nouvelle-Zélande, Suisse, Japon, Corée du Sud). Ignorée aux États-Unis, ou quand le consentement n’est pas enregistré |
prechecked_consent |
Labs. Le consentement vient d’une case pré-cochée (UE et EEE, Royaume-Uni) |
double_opt_in |
Labs. Marketing avant que l’adresse ait confirmé son abonnement : un avertissement en Allemagne, un échec si le projet exige le double opt-in |
unsubscribe_deadline |
Labs. Marketing après un désabonnement : un avertissement dans le délai autorisé (États-Unis, Canada, Singapour et Hong Kong 10 jours ouvrés, Australie et Nouvelle-Zélande 5, UE, Royaume-Uni, Suisse, Japon et Corée du Sud sans délai), un échec au-delà |
postal_address |
Labs. Aucune adresse postale trouvée dans un email marketing (États-Unis, Canada ; un avertissement) |
sender_identity |
Labs. La ligne From ne nomme aucun expéditeur (un avertissement) |
minor_marketing |
Labs. Marketing envoyé à un persona de moins de 13 ans au Royaume-Uni ou de moins de 16 ans dans l’UE et l’EEE (un avertissement) |
misleading_subject |
Labs. États-Unis, Canada, Hong Kong : un objet qui commence comme une réponse ou un transfert (Re:, Fwd:), ou qui annonce que l’offre se termine aujourd’hui alors que son coupon dure plusieurs jours de plus (un avertissement ; les lois de Washington et de Californie s’appliquent aux personas américains) |
ad_label |
Labs. Singapour : marketing envoyé à un compte qui l’a refusé dont l’objet ne commence pas par <ADV> (un avertissement) |
harmful_attachment |
Labs. Une pièce jointe est d’un type de fichier que Gmail bloque (.exe, .js, .iso et d’autres), ou un programme nommé comme un document (.pdf.exe) : échec. Les fichiers Office pouvant contenir des macros, les pages HTML ou SVG et les archives déclenchent un avertissement. La raison cite des extensions, jamais des fichiers. Ignorée sans pièce jointe |
spam_risk |
Labs. Un score de spam sur 10 (score), qui additionne les indices pris en compte par les filtres anti-spam (markers, chacun avec ses points) : échec à partir de 5, où les boîtes de réception rejetteraient probablement l’email ou le classeraient en spam, et avertissement à partir de 1,5. Les blocages majeurs comptent 5 ou 6 : DMARC en échec avec une politique de rejet ou de quarantaine, ni SPF ni DKIM valide, une pièce jointe bloquée. Puis 2 à 3 : pas de politique DMARC, pas de DKIM, presque que des images, un lien vers une adresse IP brute, un raccourcisseur de liens, du marketing sans en-tête List-Unsubscribe. Puis jusqu’à 1,5 : SPF en échec, pièces jointes à risque, désabonnement en un clic non signé, un objet marketing commençant par Re:, un objet en majuscules ou avec !!!, des formules de spam classiques, un texte de lien affichant un autre site, pas de texte brut. C’est une estimation : les boîtes de réception tiennent aussi compte de la réputation de l’expéditeur, qu’elles seules voient |
preheader |
Labs. Email marketing uniquement : pas de texte d’aperçu masqué au début du HTML, donc les boîtes de réception affichent ses premiers mots à la place, souvent « Voir dans le navigateur » (un avertissement) |
language |
Avec expect_language=de (ou pt-BR) : il est dans une autre langue. Avertissement quand il y a trop peu de texte pour le déterminer. Anglais, allemand, français, espagnol, italien, néerlandais, portugais, suédois, polonais, turc, finnois, russe, ukrainien, japonais, chinois, coréen, arabe, hébreu, grec, thaï et hindi. Ignorée sans lui |
Les vérifications marquées Labs sont exclues tant que le créateur de la clé n’active pas sa fonctionnalité Labs : Emails marketing et d’offres pour les vérifications de coupon et de marketing, et Score de spam et vérifications des pièces jointes pour harmful_attachment et spam_risk.
curl "https://api.proxymail.qa/v1/addresses/k7f2x9ab12cd@mailclub.dev/checks?type=link&expect_host=staging.example.com" \ -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY"{ "address": "k7f2x9ab12cd@mailclub.dev", "email": { "id": "…", "received_at": "2026-10-17T10:00:01Z", "service": "Supabase", "service_domain": "supabase.io", "type": "link", "subtype": "verify" }, "passed": false, "failed": ["local_links"], "warnings": ["expiry"], "checks": [ { "check": "spf", "status": "pass", "detail": "The sending server is allowed to send for supabase.io." }, { "check": "local_links", "status": "fail", "detail": "A link goes to localhost:3000, which only opens on the computer running that server." } ]}Les emails de connexion et les reçus ignorent les quatre vérifications marketing, tout comme les emails antérieurs à l’ajout des types. Une vérification sur l’email le plus récent peut désormais tomber sur une newsletter et faire échouer list_unsubscribe : passez type, subtype ou category pour vérifier l’email voulu.
Pour les emails localisés, vérifiez la version de chaque langue : ?type=code&expect_language=fr&expect_text=Bonjour%20Maya.
La réponse ne contient jamais le contenu de l’email ni ses liens : les raisons citent au plus un domaine, l’hôte d’un lien ou vos propres phrases, pour qu’un agent puisse agir sans lire le texte d’un email non fiable. Le client Node.js propose checkEmail, et l’assistant Python check_email.
Un pool de comptes pour les tests parallèles
Section intitulée « Un pool de comptes pour les tests parallèles »Gardez des comptes de test dans des états connus et laissez chaque test en emprunter un : étiquetez-les avec leur état (verified, premium, trial), puis réservez-en un. Vous obtenez un compte qui correspond à tous les filtres et que personne d’autre n’a réservé, le moins récemment utilisé d’abord, gardé pendant minutes (10 sauf demande contraire, 60 au maximum). Des tests en parallèle n’obtiennent jamais le même.
curl -s -X POST https://api.proxymail.qa/v1/addresses/checkout -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" \ -H "Content-Type: application/json" -d '{"tags":["verified","premium"],"country":"DE","minutes":5}'# { "address": "…", "persona": {…}, "tags": ["premium", "verified"], "checkout": { "token": "…", "until": "…" } }Connectez-vous avec ce compte (avec son mot de passe enregistré, pour les clés qui peuvent le lire), lancez le test, puis rendez-le avec le jeton. Le retour peut enregistrer l’état dans lequel le test l’a laissé, pour que le pool reste exact :
curl -s -X POST "https://api.proxymail.qa/v1/addresses/$ADDRESS/return" -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" \ -H "Content-Type: application/json" -d "{\"token\":\"$TOKEN\",\"add_tags\":[\"cancelled\"],\"remove_tags\":[\"premium\"]}"Le client Node.js et l’outil d’aide Python peuvent aussi créer, vérifier et réinitialiser des comptes avec des hooks depuis vos tests, pour qu’un test obtienne un compte dans l’état dont il a besoin.
Une réservation non rendue se libère d’elle-même quand son temps est écoulé : un test qui plante ne bloque donc jamais un compte longtemps ; extend lui donne plus de temps. Quand toutes les correspondances sont réservées, la réponse est 409 et indique quand la première se libère ; quand aucune ne correspond, 404. L’application montre qui a un compte et jusqu’à quand, et tout membre du projet peut le libérer.
Exécutions : un rapport pour la pull request
Section intitulée « Exécutions : un rapport pour la pull request »Une exécution regroupe un job CI ou la session d’un agent, et garde la preuve de ce que ses appels ont vu. Démarrez-en une, puis envoyez son id dans l’en-tête Proxy-Mail-QA-Run de chaque appel :
RUN=$(curl -s -X POST https://api.proxymail.qa/v1/runs -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" \ -H "Content-Type: application/json" -d '{"name":"Sign-up tests","source":"ci"}' | jq -r .id)curl -s "https://api.proxymail.qa/v1/addresses/$ADDRESS/code?since=$SUBMITTED" \ -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY" -H "Proxy-Mail-QA-Run: $RUN"curl -s -X POST "https://api.proxymail.qa/v1/runs/$RUN/finish" -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY"curl -s "https://api.proxymail.qa/v1/runs/$RUN?format=markdown" -H "Authorization: Bearer $PROXYMAIL_QA_API_KEY"GET /v1/runs/{id} compare aussi l’exécution avec les 10 dernières exécutions terminées du même nom (comparison dans JSON, une liste en haut du rapport Markdown) : e-mails beaucoup plus lents, nombre d’e-mails différent, échecs de tests nouveaux ou intermittents, vérifications nouvellement en échec, et nouvel expéditeur ou nouveau site de lien. Quand elle a vérifié des e-mails, deliverability indique combien seraient probablement rejetés ou envoyés en spam (likely_spam), pourraient aller en spam (may_spam) ou contiennent une pièce jointe que les boîtes de réception bloquent, chaque e-mail étant compté une fois selon sa dernière vérification ; le rapport Markdown dit la même chose dans la ligne sous son titre. C’est une estimation tirée de la vérification spam_risk de chaque e-mail : il faut donc activer les vérifications du score de spam et des pièces jointes dans Labs.
Chaque appel ajoute ce qu’il a vu : une adresse créée (avec les valeurs du formulaire) ou réservée dans le pool, un e-mail arrivé (le code, ou le site et le chemin du lien, tout ce qui ressemble à un jeton étant masqué, et combien de secondes après since), un décompte, des vérifications d’e-mails, des vérifications de désabonnement, d’offres et de coupons, et un e-mail qui n’est jamais arrivé ou qui est arrivé alors qu’il n’aurait pas dû. La clôture valide l’exécution sauf si l’un de ces éléments a échoué, si une note l’a fait échouer ou si vous envoyez {"failed": true}. Le rapport est Markdown, pour un commentaire de pull request ou un résumé de job, et l’exécution reste dans l’application pour tout le projet pendant 90 jours.
Pour indiquer à quel test appartient un appel, envoyez son nom, encodé en pourcentage, dans Proxy-Mail-QA-Test (par exemple sign-up%20%E2%80%BA%20sends%20one%20code) : le rapport l’affiche à côté de ce que l’appel a vu. Le reporter d’un lanceur de tests envoie les résultats à POST /v1/runs/{id}/tests : chaque test en échec ou instable sous la forme {"name", "status": "failed", "duration_ms", "error"} (un test en échec fait échouer l’exécution), puis {"totals": {"passed", "failed", "skipped", "flaky"}} pour le haut du rapport.
La collecte de preuves ne fait jamais échouer un appel : si un appel ne peut pas être ajouté (l’exécution est terminée, ou n’est pas dans le projet de cette clé), il fonctionne quand même et l’en-tête Proxy-Mail-QA-Run-Warning en donne la raison. Un client qui réessaie quand une attente revient vide envoie Proxy-Mail-QA-Run-Retry: true à chaque essai sauf le dernier, pour que seul le dernier « rien n’est arrivé » compte. Dans GitHub Actions, l’action Proxy Mail QA fait tout cela.
Quand rien n’arrive
Section intitulée « Quand rien n’arrive »Le 404 indique aussi pourquoi, dans la mesure du possible : hint nomme tout ce que nous avons refusé pour l’adresse (trop volumineux, ou au-delà d’une limite) ou, quand rien ne l’a jamais atteinte, la cause la plus probable ; refused liste ce que nous avons refusé depuis since ; blocked liste les e-mails que les serveurs de messagerie de Cloudflare ont refusés avant qu’ils nous parviennent (rejetés pour échec SPF, DKIM ou DMARC, abandonnés ou non livrés), avec le domaine de l’expéditeur et ses résultats SPF, DKIM et DMARC ; et help renvoie vers L’e-mail de test n’arrive pas ?. Les outils MCP répondent à found: false avec les mêmes message, refused et help. Pour vérifier une adresse à tout moment sans attendre, appelez GET /v1/addresses/{address}/delivery : il renvoie les e-mails reçus jusque-là, chaque refus encore enregistré et un hint. Votre application doit pouvoir envoyer un e-mail à n’importe quelle adresse : un fournisseur d’e-mails en mode sandbox ou test n’envoie qu’aux adresses que vous avez vérifiées.
Les erreurs reviennent sous la forme {"error": "not_found", "message": "…"} avec le statut correspondant : 400, 401, 402 (une organisation sans abonnement), 403 (pas d’autorisation, ou une fonctionnalité Labs désactivée), 404, 409 (un e-mail est arrivé pendant une vérification none) ou 429. Les limites de requêtes sont dans Limites et sécurité. Une erreur 500, ou des requêtes qui expirent pour tout le monde, peut indiquer un incident de notre côté : consultez notre page de statut.
Une question sur cette page ?
Posez votre question dans la langue de votre choix. La documentation peut y répondre tout de suite, ou vous pouvez l'envoyer à l'équipe Proxy Mail QA, qui répond en anglais.