API inboxsink

Des boîtes mail jetables pilotables, pour tester des tunnels d'inscription sans dépendre d'une vraie messagerie. Cinq routes, une base https://inboxsink.com, du JSON.

La route qui change quelque chose est wait : elle bloque jusqu'à l'arrivée du message. Sans elle, un test de bout en bout attend au jugé avec des sleep, et devient instable dès que la file d'envoi prend du retard.

Clé d'API gratuite

Mille appels par mois, sans carte. De quoi la brancher dans votre suite de tests et voir si ça tient.

Utilisez votre domaine, les webhooks et l’API avec l’offre Developer — 29 € par mois. Chaque site décide des adresses qu’il accepte.

Client JavaScript

Un paquet sans aucune dépendance, pour Node 18 et plus. Le code est sur GitHub.

npm i inboxsink
import { InboxSink } from 'inboxsink';
const sink = new InboxSink(process.env.INBOXSINK_API_KEY);

const inbox = await sink.createInbox();
const code = await sink.waitForOtp(inbox.id);   // '204815'

waitForOtp lève une erreur plutôt que de rendre null quand aucun code n'arrive : un test doit échouer bruyamment, pas remplir un champ vide en silence.

Guides (English), each run against the real service: Playwright · Cypress · Selenium + Python

Authentification

Un en-tête Authorization sur chaque appel. Les clés se créent depuis le tableau de bord et commencent par ibsk_.

curl -H "Authorization: Bearer ibsk_…" \
  https://inboxsink.com/v1/domains

La clé en clair n'est affichée qu'à sa création. Ensuite, seul son préfixe reste visible — nous n'en stockons que l'empreinte.

Créer une boîte

POST /v1/inboxes

Tous les paramètres sont facultatifs : domain (par défaut un domaine du pool au hasard), prefix (par défaut aléatoire), ttl_seconds (de 60 secondes à 30 jours).

curl -X POST https://inboxsink.com/v1/inboxes \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"ttl_seconds": 900}'

→ 201
{ "id": "42", "address": "9f2c1a@mailhusk.com", "expires_at": "2026-09-01T15:12:00Z" }

Une boîte créée par l'API est privée : seule la clé qui l'a créée peut la lire.

Attendre un message

GET /v1/inboxes/:id/wait

Bloque jusqu'à l'arrivée d'un message, puis répond immédiatement. timeout en millisecondes (30 000 par défaut, 120 000 au maximum), since pour ne recevoir que les messages postérieurs à un identifiant.

Répond 204 sans corps si le délai expire sans message — ce n'est pas une erreur, c'est la réponse normale à « rien n'est arrivé ».

curl -H "Authorization: Bearer $KEY" \
  "https://inboxsink.com/v1/inboxes/42/wait?timeout=45000"

→ 200
{ "message": { "id": "913", "from": "no-reply@acme.com", "subject": "…",
               "otp": "204815", "link": "https://acme.com/confirm/9f3a" } }

otp et link sont extraits pour vous. Le code n'est retenu que s'il est annoncé par un mot-clé (« code », « vérification », « connexion »…) — un montant ou une année ne sera pas confondu avec un code.

Lister les messages

GET /v1/inboxes/:id/messages

Paramètres since et limit (50 par défaut, 200 au maximum). Retourne des résumés, sans le corps des messages.

Lire un message

GET /v1/messages/:id

Le message complet : text, html, headers et la liste des pièces jointes.

Supprimer une boîte

DELETE /v1/inboxes/:id

Efface la boîte et ses messages sur-le-champ. Répond 204.

Domaine à vous

Les formulaires qui refusent les adresses jetables comparent le domaine à des listes publiques. Un domaine à vous n'est sur aucune : vos tests continuent de passer. La formule Développeur en inclut un.

  1. Ajoutez-le depuis le tableau de bord. Préférez un sous-domaine comme qa.exemple.fr : l'enregistrement MX remplace les serveurs de courrier du nom sur lequel vous le posez.
  2. Créez les deux enregistrements DNS affichés : MX 10 mx.inboxsink.com. sur le domaine, et un TXT sur _inboxsink. suivi du domaine.
  3. Cliquez sur Vérifier. Le courrier est accepté dans la minute qui suit.
// Node
const inbox = await sink.createInbox({ domain: 'qa.example.com', prefix: 'signup-7' });

# HTTP
curl -X POST https://inboxsink.com/v1/inboxes \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"domain": "qa.example.com", "prefix": "signup-7"}'

Créez ensuite vos boîtes dessus comme sur n'importe quel domaine. Toute adresse du domaine reçoit : une boîte née d'un message entrant appartient à votre compte, lisible par toutes vos clés et par personne d'autre, et conservée 30 jours.

Si l'abonnement s'arrête, le domaine cesse d'accepter du courrier. Rien n'est supprimé, et tout repart au réabonnement.

Webhooks

Plutôt que d'attendre, votre serveur peut être appelé : un POST signé pour chaque message arrivé dans une boîte de votre compte, créée par une de vos clés ou sur votre domaine. Les boîtes publiques du pool n'en déclenchent pas. Les adresses se déclarent depuis le tableau de bord (trois avec la formule Développeur), et le secret n'est affiché qu'une fois.

POST https://your-app.example/hooks/inboxsink
Content-Type: application/json
X-Inboxsink-Event: message.received
X-Inboxsink-Delivery: 913-4
X-Inboxsink-Signature: t=1789286400,v1=4df97a88e366f575…

{ "type": "message.received", "created_at": "2026-09-13T08:00:00.000Z",
  "data": { "inbox_id": "42", "address": "signup-7@qa.example.com",
            "message": { "id": "913", "from": "no-reply@acme.com", "from_name": "Acme",
                         "subject": "Your code", "otp": "204815", "link": null,
                         "received_at": "2026-09-13T08:00:00.000Z" } } }

Vérifiez la signature avant de croire le corps. C'est un HMAC-SHA256 de <t>.<corps brut> avec votre secret, le même schéma que Stripe :

import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyInboxsink(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;   // rejects replays
  return fresh && v1?.length === expected.length &&
    timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Répondez par un 2xx en moins de 10 secondes. Sinon l'appel est rejoué après 10 secondes, 1 minute et 5 minutes ; un 4xx autre que 408 et 429 n'est pas rejoué. Seuls les ports 80 et 443 sont appelés, jamais une adresse privée.

Erreurs

204Pas de contenu — pour wait, le délai a expiré sans message
400Paramètre invalide
401Clé absente, inconnue ou révoquée
403Cette boîte appartient à une autre clé
404Boîte ou message inexistant, ou déjà expiré
429Trop d'appels

Limites et durée de vie

Les boîtes du pool public vivent une heure, celles créées par l'API suivent leur ttl_seconds, jusqu'à trente jours. Passé ce délai, la boîte et ses messages sont supprimés — il n'y a pas d'archive à récupérer ensuite.

Les messages de plus de dix mégaoctets sont refusés à la réception. Les adresses d'un domaine inconnu sont rejetées pendant la session SMTP, sans générer de rebond.

Ces boîtes ne peuvent pas envoyer de courrier, et c'est délibéré : un message émis depuis un domaine jetable n'arriverait nulle part.