MailFlatDocs
Documentation/Test frameworks & CI/Postman

Using MailFlat with Postman

Chain three requests (create an inbox, trigger your flow, read the code) with the address carried between them in a collection variable.

How it fits together

No SDK involved: MailFlat is a plain REST API, so Postman drives it directly. The only trick worth knowing is polling, which a small test script handles with setNextRequest. Two Postman specifics decide whether that loop behaves. The sandbox does not block on setTimeout, so a script cannot pause itself between attempts and the pause has to come from the runner instead. And collection variables outlive the run, so a counter left behind by a failed run is still sitting there when the next one starts.

Create the inbox

POST to /inboxes, then store the address for the requests that follow. This endpoint needs a key with the inbox:manage scope; a newly issued key carries inbox:read only and answers 403 here.
JavaScript
// Tests tab of "Create inbox"
const body = pm.response.json();
pm.collectionVariables.set("address", body.address);
 
pm.test("inbox created", () => pm.response.to.have.status(200));

Poll for the code

setNextRequest loops back onto this same request until otp_code shows up. Reset the counter on every exit from the loop, not only the successful one, and give up early on an encrypted inbox rather than spending twenty attempts on a box the server is never allowed to read.
JavaScript
// Tests tab of "Get latest" → GET {{baseUrl}}/inboxes/{{address}}/latest
const res = pm.response.json();
const tries = Number(pm.collectionVariables.get("tries") || 0);
 
if (res.encrypted) { // end-to-end encrypted box: no code, ever
pm.collectionVariables.unset("tries");
pm.expect.fail(res.note);
} else if (res.email?.otp_code) {
pm.collectionVariables.set("otp", res.email.otp_code);
pm.collectionVariables.unset("tries");
} else if (tries < 20) {
pm.collectionVariables.set("tries", tries + 1);
postman.setNextRequest("Get latest"); // poll again
} else {
pm.collectionVariables.unset("tries"); // or the next run starts at 20
pm.expect.fail("no OTP arrived in time");
}
The pause between attempts comes from the runner, not from this script: set Delay in the Collection Runner, or pass --delay-request 3000 to newman. A setTimeout here schedules a callback and returns immediately, so the loop would run twenty attempts in a fraction of a second and report nothing arrived.

Environment

Two variables and the auth header, set once on the collection.
JSON
{
"baseUrl": "https://mailflat.net/api/v1",
"apiKey": "mf_live_..."
}
Add X-API-Key: {{apiKey}} as a collection-level header so every request inherits it.

Clean up

A last request in the collection deletes the inbox. Give it retention_hours on create as well, so an aborted run still cleans itself up.
JavaScript
// "Delete inbox" → DELETE {{baseUrl}}/inboxes/{{address}}
// Tests tab:
pm.test("inbox removed", () => pm.response.to.have.status(200));
pm.collectionVariables.unset("address");
pm.collectionVariables.unset("otp");

Worth knowing

Store the key as a Postman secret variable, not in the collection JSON you commit. An account key grants access to every inbox you own, so a collection export with the key inside it is the whole account.
The same collection runs headless in CI with newman. Give it --delay-request so the polling loop paces itself the way the Collection Runner's Delay setting does interactively.
The header is X-API-Key. A Bearer token is a browser session rather than an API key and will not authenticate these endpoints, which is the usual reason a request that works in the dashboard returns 401 here.
Collection variables survive between runs. Anything the loop sets should be unset on every exit path, or the next run inherits it and behaves strangely before the first request even goes out.
The create response carries the retention that was actually applied, which is not always the one you asked for: a request above your plan maximum is capped silently rather than refused. Assert on retention_hours in the create test if it matters.
The address is permanent until you delete it. A collection meant only for reading can therefore skip creation entirely, run against a fixed address, and use a key with inbox:read so no request in it is able to delete anything.

When it goes wrong

SymptomWhyWhat to do
The polling loop finishes instantly and reports no OTPThe script tried to pause with setTimeout. Postman's sandbox does not block on it, so all twenty attempts fire within a moment of each other and the mail has not arrived yet.Set Delay in the Collection Runner, or pass --delay-request to newman. The tell is the whole loop finishing far faster than the mail could plausibly arrive.
A run fails immediately after a previous run failedThe tries counter was left at its maximum by the run before, because the failure path never unset it. The first response is then already over the limit.Unset the counter on every exit path, as above. Clearing collection variables between runs hides this rather than fixing it.
Twenty attempts against a box that will never produce a codeThat inbox is end to end encrypted. The server cannot read the body, so the response carries encrypted and a note instead of an email, and the loop keeps waiting for something that cannot arrive.Check res.encrypted first and fail with the note, as above. Boxes opened through this API are always plain text, so an encrypted one was created in the dashboard on an account with encryption on.
Create returns 403 while reading works fineReading needs inbox:read and a fresh key has it. Creating and deleting need inbox:manage, which is not granted by default.Reissue the key with inbox:manage, or drop the create and delete requests and run the collection against a fixed address.
Get latest returns 403 for an address you are sure existsThree situations answer with the same 403 deliberately: the address does not exist, it was deleted, or it belongs to another account. Separating them would reveal which addresses exist to someone who does not own them.The message says all three and points at GET /api/v1/inboxes. Run that first and compare, which also confirms the key is the one you think it is.