blog
Selenium Email Testing for Transactional Flows and MFA
Learn how to use Selenium email testing to verify signup, password reset, OTP, and MFA flows with real inboxes and deterministic message assertions.
A Selenium test can submit a signup form successfully while the verification email goes to the wrong address or contains an unusable code. To test the full journey, the browser needs a controlled inbox and must use the code or link that the application actually delivered.
MailSlurp supplies that inbox through an API. Selenium continues to drive your application; the mailbox step waits for the matching message without opening another email website. The same pattern covers signup, password reset, email OTP and MFA recovery.
Quick answer
Selenium email testing should let you:
- generate a unique inbox or phone number
- use that address in the browser flow
- wait for the exact message sent by the application
- extract a link, code, or content fragment
- continue the Selenium journey using the extracted artifact
If you only validate the form submit and never assert the message content, you are not covering the full transaction path.
Common Selenium email testing scenarios
The most common workflows are:
- signup verification
- password reset
- invite acceptance
- email OTP
- MFA recovery
- receipt and notification validation
Selenium remains popular for these flows in Java, C#, Ruby, and enterprise grid environments, especially when teams already run browser automation at scale.
Why Selenium email tests become brittle
The biggest causes are:
- one shared inbox for multiple tests
- hard-coded sleep statements
- weak subject-only matching
- browser success assertions before the message is actually verified
- local SMTP tools that do not represent production timing or formatting
The framework is not the real issue. The message layer usually is.
A safer Selenium email testing architecture
Use this pattern:
- create an isolated inbox or number
- open the app in Selenium
- submit the flow with the generated contact target
- wait for the matching message through the inbox API
- extract the token, code, or link
- continue the Selenium test and assert success
This keeps the browser steps deterministic while moving message waits into a system built for that job.
Selenium email testing for password resets
A password-reset test needs to prove that the delivered link changes the password for the requested account.
You should assert:
- reset email arrival
- sender identity
- message subject and body
- reset link extraction
- link usability
- final password change success
When teams skip those checks, they often miss broken templates, expired links, and misrouted mail.
Selenium email testing for MFA and one-time codes
Selenium is also a strong fit for MFA journeys when your team needs real browser coverage in Java or C#.
In those cases, you should validate:
- code arrival timing
- code format
- retry and resend behavior
- fallback path correctness
- post-authenticated state
For TOTP and related flows, see Testing TOTP one time passwords.
Complete an email OTP in Selenium
This Python example uses Selenium for the browser and the MailSlurp REST API for email. Install Selenium with python -m pip install selenium. Call verify_email_otp(driver, app_origin, api_key) from your existing test, supplying its WebDriver, your test application's origin and a MailSlurp key from the test runner's secret environment. The caller remains responsible for closing its browser.
Adapt /signup, /account, the input names, data-testid selectors, subject and code wording to your application. This example expects the account page to display the signed-in email address. It keeps the email wait separate from Selenium's explicit element waits, so a slow inbox does not become a fixed browser sleep.
import json
import re
import warnings
from datetime import datetime, timezone
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
def verify_email_otp(driver, app_origin, api_key):
def api(method, path, payload=None):
data = None if payload is None else json.dumps(payload).encode()
request = Request("https://api.mailslurp.com" + path, data=data, method=method,
headers={"x-api-key": api_key, "Content-Type": "application/json"})
with urlopen(request, timeout=70) as response:
body = response.read()
return json.loads(body) if body else None
inbox = api("POST", "/inboxes/withOptions", {"expiresIn": 600000})
wait = WebDriverWait(driver, 15)
origin = app_origin.rstrip("/")
try:
since = datetime.now(timezone.utc).isoformat()
driver.get(origin + "/signup")
wait.until(EC.visibility_of_element_located((By.NAME, "email"))).send_keys(inbox["emailAddress"])
driver.find_element(By.CSS_SELECTOR, "[data-testid='send-code']").click()
query = urlencode({"inboxId": inbox["id"], "since": since,
"timeout": 60000, "unreadOnly": "false"})
email = api("POST", "/waitForMatchingFirstEmail?" + query,
{"matches": [{"field": "SUBJECT", "should": "EQUAL",
"value": "Verify your account"}]})
assert inbox["emailAddress"] in email["to"], "Wrong recipient"
text = api("GET", "/emails/" + email["id"] + "/textLines?decodeHtmlEntities=true")
codes = re.findall(r"Your verification code is:\s*(\d{6})(?!\d)", "\n".join(text["lines"]))
assert len(codes) == 1, "Expected exactly one six-digit verification code"
wait.until(EC.visibility_of_element_located((By.NAME, "code"))).send_keys(codes[0])
driver.find_element(By.CSS_SELECTOR, "[data-testid='verify-code']").click()
wait.until(EC.url_to_be(origin + "/account"))
account = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='account-email']")))
assert account.text == inbox["emailAddress"], "Wrong signed-in account"
finally:
try:
api("DELETE", "/inboxes/" + inbox["id"])
except Exception:
warnings.warn("Inbox cleanup failed; its configured expiry remains in place.")
The six-digit code is kept as a string, preserving a leading zero. The template-specific phrase prevents an unrelated number in a footer or order total from being mistaken for the OTP. Missing or duplicate matches fail before submission. A fresh inbox, expected subject and request timestamp prevent another test or an older message from completing this attempt.
Add separate tests for a wrong code, an expired code, reuse after success, and a resend. Each case should assert the application's actual rejection state as well as the absence of a signed-in session; do not change production token lifetimes to make a test faster.
Language support matters in Selenium teams
One reason Selenium stays relevant is language diversity.
MailSlurp examples cover Selenium suites using:
- Java Maven
- Java TestNG
- C# .NET
- Rust Selenium
- SpecFlow and MSTest
Examples and guides:
- Receive emails in Java Selenium tests
- Test emails in Selenium with .NET and C#
- Serenity email account testing with JBehave and Java
Selenium email testing vs Playwright and Cypress
Choose Selenium when:
- you already run Java or C# browser suites
- you need wide browser-grid compatibility
- your QA organization is built around enterprise test infrastructure
Choose Playwright or Cypress when:
- the team prefers TypeScript or JavaScript-first flows
- local developer ergonomics matter more than cross-stack parity
- you want faster feedback in frontend-owned suites
The message-testing pattern is almost identical across all three.
What to compare in a Selenium email testing tool
Look for:
- official API support in your language
- inbox lifecycle control
- SMS support if auth flows require it
- robust wait and search primitives
- HTML, text, and attachment access
- clean fit with CI or grid infrastructure
Check the SDK version and timeout behavior in the language your suite uses, then run the same inbox lifecycle in CI and on your browser grid.
How MailSlurp helps
MailSlurp is built for Selenium teams that need:
That gives browser suites a controlled way to create targets, receive messages, and verify user-visible outcomes end to end.
FAQ
How do I test emails in Selenium?
Create a unique inbox, use it in the browser flow, wait for the matching message through an inbox API, extract the token or link, and continue the Selenium test.
Is Selenium email testing still relevant?
Yes. It remains especially relevant for Java, C#, and enterprise test-grid environments.
Can Selenium test MFA and OTP flows?
Yes. Selenium can validate email and SMS-based auth paths when paired with inbox or phone-number APIs.
What is the main source of flaky Selenium email tests?
Shared inboxes, brittle sleeps, and weak message matching are the biggest causes.