/selenium Email Testing
Drive a real browser with Selenium WebDriver and pull OTP codes and verification links out of disposable inboxes via the OpenInbox API — shown in Python, with notes for Java and C#.
Selenium automates the browser, but it can't open the mailbox the verification email lands in. The fix is to give each test a disposable inbox over the OpenInbox REST API: WebDriver fills the form, then a few lines of HTTP polling pull the code or link straight out of the email body. The examples below are real Selenium + Python and run against the live OpenInbox API.
How the flow works
- Create a disposable inbox: POST /api/inbox (no auth) → { id, email, expiresAt }.
- WebDriver types that address into your signup/login form and submits it.
- Poll GET /api/inbound/api/emails?inboxEmail=<email> with X-API-KEY until the email arrives.
- Extract the OTP (or link) from the email textBody with a regex.
- WebDriver enters the code / visits the link and you assert the result.
Setup
Install Selenium plus requests (for the API calls). webdriver-manager keeps the browser driver in sync so the same test runs locally and in CI. The inbox-polling endpoint is premium, so put your key in an environment variable.
pip install selenium requests webdriver-manager
export OPENINBOX_API_KEY="your-premium-api-key"An OpenInbox helper module
Keep the API out of your test logic with a small module. create_inbox creates a fresh inbox; wait_for_otp polls the inbox and returns the first 4–8 digit code it finds in textBody.
import os, re, time, requests
API = "https://api.openinbox.io/api"
HEADERS = {"X-API-KEY": os.environ["OPENINBOX_API_KEY"]}
def create_inbox() -> dict:
"""Create a disposable inbox (no auth required)."""
r = requests.post(f"{API}/inbox")
r.raise_for_status()
return r.json() # { "id", "email", "expiresAt", ... }
def wait_for_otp(inbox_email: str, timeout: int = 30) -> str:
"""Poll the inbox until an email arrives, then return the OTP from textBody."""
deadline = time.time() + timeout
while time.time() < deadline:
r = requests.get(
f"{API}/inbound/api/emails",
params={"inboxEmail": inbox_email},
headers=HEADERS,
)
emails = r.json().get("emails", [])
if emails:
match = re.search(r"\b\d{4,8}\b", emails[0].get("textBody") or "")
if match:
return match.group(0)
time.sleep(2)
raise TimeoutError("No OTP email arrived in time")The full Selenium + pytest test
Now the test reads top to bottom: create an inbox, run the UI signup with WebDriver, fetch the OTP from the API, type it in, and assert you reached the dashboard. WebDriverWait handles UI timing; wait_for_otp handles the email.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from openinbox import create_inbox, wait_for_otp
def test_email_otp_signup():
inbox = create_inbox()
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
# 1. Open signup and submit the disposable address
driver.get("https://staging.yourapp.com/signup")
driver.find_element(By.NAME, "email").send_keys(inbox["email"])
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
WebDriverWait(driver, 10).until(
EC.visibility_of_element_located(
(By.XPATH, "//*[contains(text(), 'Check your email')]")
)
)
# 2. Pull the OTP from the inbox and enter it
otp = wait_for_otp(inbox["email"])
driver.find_element(By.CSS_SELECTOR, "[data-testid=otp]").send_keys(otp)
driver.find_element(By.XPATH, "//button[contains(., 'Verify')]").click()
# 3. Assert we reached the dashboard
WebDriverWait(driver, 15).until(EC.url_contains("/dashboard"))
assert driver.find_element(
By.CSS_SELECTOR, "[data-testid=user-menu]"
).is_displayed()
finally:
driver.quit()Other languages (Java, C#)
Selenium is the same REST flow in every binding — only the HTTP client changes. In Java, create the inbox with java.net.http.HttpClient and poll /api/inbound/api/emails with an X-API-KEY header; in C#, use HttpClient the same way. The browser-driving half stays pure Selenium. See the language-specific API guides for ready-made clients.
Running headless in CI
The --headless=new flag runs Chrome without a display, and webdriver-manager fetches a matching driver, so the test works unchanged on a GitHub Actions runner. Store your key as a secret and expose it as OPENINBOX_API_KEY.
name: Selenium E2E
on: [push]
jobs:
selenium:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install selenium requests webdriver-manager pytest
- run: pytest test_signup.py
env:
OPENINBOX_API_KEY: ${{ secrets.OPENINBOX_API_KEY }}Common pitfalls
- •Use a polling loop with a deadline (like wait_for_otp) rather than time.sleep() guesses — emails usually land in 2–5s but occasionally take longer, and a fixed sleep is either flaky or slow.
- •Creating an inbox needs no auth, but GET /api/inbound/api/emails is premium — a 401/403 means the X-API-KEY env var is missing or the plan is not paid.
- •WebDriverWait covers UI timing only; it cannot wait for an email. Keep the two waits separate (WebDriverWait for the page, wait_for_otp for the inbox).
- •The \b\d{4,8}\b regex matches any 4–8 digit run, so it can catch unrelated numbers. If the email has other digits, anchor it (e.g. r"code[:\s]*(\d{6})").
- •In CI, a Chrome/driver version mismatch is the most common failure — webdriver-manager (or Selenium Manager in Selenium 4.6+) resolves the right driver automatically.
Frequently Asked Questions
Can Selenium read emails on its own?
No — Selenium only controls the browser. To verify email flows you create a disposable inbox via the OpenInbox API and poll it with an HTTP client (requests in Python), all from inside the same test.
Do I need an API key?
Creating an inbox (POST /api/inbox) is unauthenticated. Polling received emails (GET /api/inbound/api/emails) requires a premium X-API-KEY, supplied via the OPENINBOX_API_KEY environment variable.
How do I extract the OTP in Python?
Run re.search(r"\b\d{4,8}\b", email["textBody"]) against the email body returned by the poll endpoint. There is no separate parsed field — it is a one-line regex on textBody.
Does this work with headless Chrome and in CI?
Yes. Add the --headless=new argument and the test runs without a display. Because the API calls are plain HTTPS, no SMTP ports or special networking are needed on the runner.
Can I use this with Selenium in Java or C#?
Yes — the browser steps are identical; only the HTTP client for the API differs. Use java.net.http.HttpClient (Java) or HttpClient (C#) to call POST /api/inbox and GET /api/inbound/api/emails with the X-API-KEY header.
How long do inboxes last?
Free inboxes expire after one hour, which is ample for a test. Create a fresh inbox per test so parallel runs stay isolated.
Related Pages
Explore More
Your private inbox is one click away
Free, instant, no registration needed. Works with any service. Expires automatically after 1 hour.
