Using MailFlat with Selenium
Fill a signup form with a real address, then type the code that really arrived, in Java or Python, next to the WebDriver calls you already have.
How it fits together
Selenium drives the browser and nothing else, so the mailbox has to come from somewhere. MailFlat sits beside the driver: open an inbox, send its address through the form, wait for the code, type it in. Two naming details are worth knowing before the first run, because both fail quietly rather than loudly. In Java, the one argument create takes a label, which is a display name rather than the address, and on Free a label is ignored altogether. And whichever client you use, the prefix is cleaned before it becomes an address: anything outside lowercase letters, digits and hyphens is dropped without complaint, so a prefix built from a class name does not survive intact.
Java, with JUnit 5
A fresh inbox per test, torn down in the AfterEach hook. Build the options explicitly rather than calling create with a bare string: the string form sets the label, and the address you meant to control is then generated for you.
import net.mailflat.MailFlat;
import net.mailflat.Inbox;
import net.mailflat.CreateInboxOptions;
import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
class SignupTest {
static MailFlat mf = new MailFlat(System.getenv("MAILFLAT_API_KEY"));
Inbox inbox;
@BeforeEach
void openInbox() {
inbox = mf.create(CreateInboxOptions.builder()
.prefix("selenium-" + System.nanoTime()) // prefix, not label
.retentionHours(2)
.build());
}
@AfterEach
void closeInbox() {
inbox.delete();
}
@Test
void signsUpWithARealCode() {
driver.get("https://staging.example.com/signup");
driver.findElement(By.id("email")).sendKeys(inbox.address());
driver.findElement(By.id("submit")).click();
driver.findElement(By.id("code")).sendKeys(inbox.waitForOtp(60));
Assertions.assertTrue(driver.getCurrentUrl().endsWith("/dashboard"));
}
}
Python, with pytest
Same shape, in a fixture. Note that the two clients count differently: the Java wait above takes seconds and so does this one, while the JavaScript client takes milliseconds, so the same literal means very different things across our examples.
import os, time, pytest
from mailflat import MailFlat
from selenium.webdriver.common.by import By
mf = MailFlat(api_key=os.environ["MAILFLAT_API_KEY"])
@pytest.fixture
def inbox():
box = mf.create(prefix=f"selenium-{int(time.time())}", retention_hours=2)
yield box
box.delete()
def test_signs_up_with_a_real_code(driver, inbox):
driver.get("https://staging.example.com/signup")
driver.find_element(By.ID, "email").send_keys(inbox.address)
driver.find_element(By.ID, "submit").click()
driver.find_element(By.ID, "code").send_keys(inbox.wait_for_otp(timeout=60))
assert driver.current_url.endswith("/dashboard")
Worth knowing
waitForOtp blocks the calling thread, so there is no need for an explicit WebDriverWait around it. Wrapping it in one only adds a second clock that can expire first and report a browser problem instead of a mail one.
Grid and parallel runs are safe: the isolation comes from one inbox per test, not from the driver. What does not scale automatically is the plan allowance, since Free permits three agent inboxes at once regardless of how many nodes the grid has.
The Java client waits in seconds and defaults to thirty when called with no argument. The Python client also waits in seconds but takes keyword arguments only, so wait_for_otp(60) raises a TypeError while wait_for_otp(timeout=60) is correct.
The prefix is cleaned before it becomes an address: uppercase is lowered and anything that is not a letter, digit or hyphen is removed. Two prefixes that differ only in punctuation can therefore collapse onto the same address, and the second one is refused as taken.
The address is permanent until you delete it. Suites that drive a browser are slow enough that reusing one known address is tempting, and it is supported, but then delete the messages between tests rather than the box.
When it goes wrong
| Symptom | Why | What to do |
|---|---|---|
| The address bears no relation to the prefix that was asked for | In Java the single argument form of create sets the label, not the prefix, and on Free labels are ignored entirely because custom names are a paid feature. The address is generated instead. | Use the options builder with prefix as shown. The create response also carries ignored_fields and a sentence saying which parts of the request had no effect, which is the quickest confirmation. |
| Creating an inbox is refused as taken, on a name nobody has used | The prefix was cleaned first. A name with an underscore, a dot or uppercase becomes a different, shorter string, and it can be the same string another test already produced. | Build prefixes from lowercase letters, digits and hyphens only, and add something genuinely unique such as nanoTime. Then read the address off the returned inbox rather than reconstructing it. |
| sendKeys types the code into the field and the app rejects it | The code arrived from a message that was already in the box before this test triggered anything, because the box outlived the previous test. The wait returns the newest message it can see and does not know which run produced it. | Open the box in the per test hook as shown, so nothing survives between tests. This one fails in the application rather than in the client, which is what makes it hard to place. |
| The fourth parallel node cannot open an inbox | Free allows three agent inboxes at once. This is a plan allowance rather than a rate limit, so the refusal is a 400 naming the plan and the number, not a 429. | Delete each box in the teardown hook so the allowance frees as nodes finish, or run the grid against one shared address. Paid plans do not cap agent inboxes. |
| The wait fails saying mail arrived without a readable code | A message landed but no code could be extracted from it. That is a parsing miss, not a delivery failure, and the browser side of the test is irrelevant to it. | The error quotes the newest message's subject. Read inbox.latest() and pull the code yourself, and send us the format so it stops needing a workaround. |
See also
Java
Built on java.net.http, no HTTP dependency of its own. Made for Selenium and JUnit suites that need a real address per test.
PythonThe official Python client wraps the REST API in two calls: open an inbox, wait for the code. Works in pytest, unittest, Robot Framework or a plain script.
One-time codesMailFlat pulls the verification code out of the message for you and hands it over as a field, so your test never runs a regex against an email body.