MailFlatDocs
Documentation/Common test cases/Waiting and timeouts

Waiting for email without flaky tests

Mail is asynchronous, so every email test waits for something. The difference between a solid suite and a flaky one is how that wait is written.

How it works

Three rules. Always set a deadline. Fail loudly when the deadline passes, naming the address you were waiting on. Never sleep a fixed number of seconds and hope — that is either slower than it needs to be or shorter than it needs to be, and usually both on different days.

Wait with a deadline

The official clients poll for you and raise when the deadline passes.
otp = inbox.wait_for_otp(timeout=60, poll_interval=2) # seconds
msg = inbox.wait_for_message(timeout=60)

Fail loudly, not silently

A helper that returns nothing on timeout turns a missing email into a confusing error three lines later.
from mailflat import OTPTimeoutError
 
try:
otp = inbox.wait_for_otp(timeout=60)
except OTPTimeoutError:
pytest.fail(f"no code reached {inbox.address} in 60s")

Or stop polling entirely

If your runner can accept an inbound request, let the message come to you instead.
curl
# register once, then react instantly instead of polling
curl -s -X POST https://mailflat.net/api/webhooks \
-H "Authorization: Bearer $MAILFLAT_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://ci.example.com/hooks/mailflat"}'
Webhooks are registered with a session token, not an account key. Full setup, payload shape and signature verification: see the webhooks guide.

Worth knowing

Polling budget is rarely the problem
The limit that matters is 500 requests per hour per IP. At a two to three second interval with a real deadline, a suite of hundreds of tests stays comfortably inside it.