MailFlatDocs
Documentation/Languages/Java

Email testing with 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.

Before you begin

  1. A MailFlat account
    Free to start, no card. Every plan can open inboxes from the API.
  2. An account API key
    Agents → API keys in the dashboard. It looks like mf_live_… and goes in the X-API-Key header. Keep it in an environment variable, never in the repo. See API keys and authentication.
  3. Java 17+
    That is the whole toolchain requirement.

Install

Java
<!-- pom.xml: add the JitPack repository first, or resolution fails -->
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
 
<dependency>
<groupId>com.github.onderyentar21</groupId>
<artifactId>mailflat-sdks</artifactId>
<version>v0.1.1</version>
</dependency>
 
<!-- Gradle -->
<!-- repositories { maven { url "https://jitpack.io" } } -->
<!-- implementation "com.github.onderyentar21:mailflat-sdks:v0.1.1" -->
Package: mailflat-sdks

Your first inbox and one-time code

The whole loop: open an address, let your app mail it, read the code back, clean up.
Java
import net.mailflat.MailFlat;
import net.mailflat.Inbox;
 
MailFlat mf = new MailFlat(System.getenv("MAILFLAT_API_KEY"));
 
// 1. a real, deliverable address
Inbox inbox = mf.create("signup");
System.out.println(inbox.address()); // signup@a7f2c.mailflat.net
 
// 2. your app sends the code to that address
myApp.register(inbox.address());
 
// 3. read it back, no mocking anywhere
String otp = inbox.waitForOtp(30); // seconds
System.out.println(otp); // "482913"
 
// 4. done with it
inbox.delete();
Line by linewhat each step of this Java example does
create(...)
Returns immediately with a real, deliverable address. Nothing is queued or simulated.
retention_hours
Optional. Messages purge themselves after it, so a skipped teardown never leaks.
wait_for_otp
Polls for you and fails loudly on timeout, instead of returning nothing three lines before the real error.
delete()
Optional but tidy. Retention would clean up anyway.
The address is real
Mail actually travels: SMTP, DKIM, the lot. Nothing is stubbed, so a broken template or a misconfigured sender fails here exactly like it would in production.

Waiting for the code

We extract the one-time code server-side and hand it to you as a field, so you never write a regex against an email body.
Java
// seconds, not milliseconds
String otp = inbox.waitForOtp(30);
 
// need the whole message?
Message msg = inbox.waitForMessage(30);
System.out.println(msg.subject() + " from " + msg.sender());
System.out.println(msg.text());
 
// throws on timeout, so a missing email fails the test loudly
try {
inbox.waitForOtp(5);
} catch (MailFlatException e) {
System.out.println("no code arrived: " + e.getMessage());
}
Always set a timeout
A poll loop without a deadline turns a missing email into a hung job. Fail loudly instead: the error message should name the address you were waiting on.

Reading every message

Java
for (Message msg : inbox.messages()) { // newest first
System.out.println(msg.subject() + " " + msg.receivedAt());
System.out.println(msg.text()); // plain text body
System.out.println(msg.html()); // HTML body
System.out.println(msg.otp()); // extracted code, or null
}
 
Optional<Message> latest = inbox.latest(); // empty until the first message lands
latest.filter(m -> m.subject().contains("Reset"))
.ifPresent(m -> inbox.deleteMessage(m.id()));
FieldTypeWhat it is
subjectstringSubject line
senderstringFrom address
body_textstringPlain text body
body_htmlstringHTML body
otp_codestring | nullOne-time code, extracted by us
to_addressstringThe exact address it was sent to, tag included
tagstring | nullPlus-addressing tag, if the sender used one
received_atISO 8601When it landed
is_encryptedbooleanTrue on end-to-end encrypted inboxes, where body and code are unavailable

Sending mail from the inbox

Useful in reverse: point your own inbound pipeline at a MailFlat address and check that it handles what arrives.
Java
// mail leaves from the inbox address, DKIM-signed by our own MTA
inbox.send("someone@example.com", "Welcome", "Plain text body");
 
// with an HTML body
inbox.send("someone@example.com", "Welcome", "Plain text", "<p>HTML body</p>");

Cleaning up

Java
inbox.delete(); // inbox and every message, immediately
 
// or leave it: messages expire on their own at the retention you asked for
Inbox inbox = mf.create(CreateInboxOptions.builder()
.label("ci")
.retentionHours(2)
.build());
Two safety nets, use both
Delete in teardown so the list stays readable, and set retention_hours so a crashed run still cleans itself up.

In a test suite

Works with JUnit 5, Selenium, TestNG, Cucumber and anything else that gives you a setup and teardown hook.
Java
// SignupTest.java — a fresh inbox per test, alongside Selenium
import net.mailflat.MailFlat;
import net.mailflat.Inbox;
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("ci-" + java.util.UUID.randomUUID().toString().substring(0, 8));
}
 
@AfterEach
void closeInbox() {
inbox.delete();
}
 
@Test
void userCanSignUpWithARealCode() {
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"));
}
}
Line by linewhat each step of this Java example does
unique prefix
One inbox per test, so parallel workers never read each other's mail.
teardown hook
Runs even when the test fails — that is exactly when inboxes get left behind.
real code, real email
No test-mode backdoor in your app: the path under test is the one your users take.

Java reference

MethodWhat it does
mf.create() / mf.create(label) / mf.create(CreateInboxOptions)Open an inbox, returns Inbox
mf.list()Every inbox this key opened
mf.inbox(address)Attach to an existing address without an API call
inbox.waitForOtp(seconds)Poll until a code arrives, throws on timeout
inbox.waitForMessage(seconds)Same, but returns the whole Message
inbox.messages() / inbox.latest()List<Message> (newest first) / Optional<Message>
inbox.send(to, subject, body[, html])Send from this address, DKIM-signed
inbox.delete() / inbox.deleteMessage(id)Drop the inbox / one message
Full endpoint reference, including error shapes and rate limits: Agent API and MCP.