Mock Inbox - OTP/2FA Testing
Practice automating OTP verification flows with our mock inbox API
📧How Mock Inbox Works
In real applications, OTP codes are sent via email or SMS. Our mock inbox simulates this by storing OTPs in memory and providing an API endpoint to retrieve them for testing.
The Flow:
- Send OTP →
POST /otp/send - Retrieve OTP →
GET /otp/inbox/:email - Verify OTP →
POST /otp/verify
🌐 API Base URL
https://www.passthenote.com/api/gatewayInteractive Demo
1Send OTP
🎯 Enterprise Testing Scenarios
API Endpoints
/otp/sendGenerate and send OTP code to an email address.
Request Body:
{
"email": "testuser@passthenote.com",
"purpose": "login" // optional: login, signup, password-reset, verification
}Response (200 OK):
{
"message": "OTP sent successfully",
"email": "testuser@passthenote.com",
"expiresIn": "10 minutes"
}/otp/inbox/:emailRetrieve OTP from mock inbox (for testing automation).
Example:
GET /otp/inbox/testuser@passthenote.com
Response (200 OK):
{
"email": "testuser@passthenote.com",
"otp": "123456",
"purpose": "login",
"expiresIn": "574 seconds"
}/otp/verifyVerify an OTP code. Code is deleted after successful verification (one-time use).
Request Body:
{
"email": "testuser@passthenote.com",
"otp": "123456"
}Success Response (200 OK):
{
"message": "OTP verified successfully",
"verified": true
}Error Response (401 Unauthorized):
{
"error": "Invalid OTP",
"verified": false,
"code": "INVALID_OTP"
}Automation Examples
🔷Selenium (Java)
import okhttp3.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.By;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class OTPTest {
private static final String BASE_URL = "https://www.passthenote.com/api/gateway";
@Test
public void testOTPVerification() throws Exception {
WebDriver driver = new ChromeDriver();
OkHttpClient client = new OkHttpClient();
String email = "testuser@passthenote.com";
// Step 1: Navigate to app and trigger OTP send
driver.get("https://www.passthenote.com/app/login");
driver.findElement(By.id("email")).sendKeys(email);
driver.findElement(By.id("request-otp-button")).click();
// Step 2: Wait for "OTP sent" confirmation
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("otp-sent-message")));
// Step 3: Poll for OTP with proper wait strategy
String otp = pollForOTP(client, email, 10);
System.out.println("Retrieved OTP: " + otp);
// Step 4: Enter OTP in the UI
driver.findElement(By.id("otp-input")).sendKeys(otp);
driver.findElement(By.id("verify-button")).click();
// Step 5: Verify success
WebElement success = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("success-message"))
);
Assert.assertTrue(success.isDisplayed());
driver.quit();
}
// Helper: Poll for delayed OTP (Enterprise scenario)
private String pollForOTP(OkHttpClient client, String email, int maxAttempts) throws Exception {
for (int i = 0; i < maxAttempts; i++) {
Request request = new Request.Builder()
.url(BASE_URL + "/otp/inbox/" + email)
.build();
Response response = client.newCall(request).execute();
if (response.code() == 202) {
// Email delayed - wait and retry
System.out.println("Email delayed, retrying... (" + (i+1) + "/" + maxAttempts + ")");
Thread.sleep(1000);
continue;
}
if (response.code() == 200) {
String jsonData = response.body().string();
JsonObject json = JsonParser.parseString(jsonData).getAsJsonObject();
// Check spam folder
String folder = json.get("folder").getAsString();
if ("SPAM".equals(folder)) {
System.out.println("⚠️ Warning: Email went to spam folder!");
}
return json.get("otp").getAsString();
}
}
throw new RuntimeException("Failed to retrieve OTP after " + maxAttempts + " attempts");
}
}🎭Playwright (TypeScript) - With Magic Link
import { test, expect } from '@playwright/test';
const BASE_URL = 'https://www.passthenote.com';
// Helper: Poll for delayed OTP
async function pollForOTP(request: any, email: string, maxAttempts = 10) {
for (let i = 0; i < maxAttempts; i++) {
const response = await request.get(
`${BASE_URL}/api/gateway/otp/inbox/${encodeURIComponent(email)}`
);
if (response.status() === 202) {
console.log(`Email delayed, attempt ${i+1}/${maxAttempts}...`);
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
if (response.ok()) {
const data = await response.json();
// Check spam folder
if (data.folder === 'SPAM') {
console.warn('⚠️ Email went to spam folder!');
}
return data;
}
}
throw new Error('Failed to retrieve OTP');
}
test('OTP verification with polling', async ({ page, request }) => {
const email = 'testuser@passthenote.com';
// Step 1: Navigate and request OTP with delay
await page.goto(`${BASE_URL}/app/login?testDelay=true`);
await page.fill('[data-testid="email-input"]', email);
await page.click('[data-testid="request-otp"]');
await page.waitForSelector('[data-testid="otp-sent-message"]');
// Step 2: Poll for OTP (handles 202 delayed response)
const otpData = await pollForOTP(request, email);
console.log('OTP Retrieved:', otpData.otp);
// Step 3: Enter OTP in UI
await page.fill('[data-testid="otp-input"]', otpData.otp);
await page.click('[data-testid="verify-button"]');
// Step 4: Verify success
await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
});
test('Magic Link extraction (Interview Challenge)', async ({ page, request }) => {
const email = 'testuser@passthenote.com';
// Step 1: Trigger magic link email
await request.post(`${BASE_URL}/api/gateway/otp/send`, {
data: { email, purpose: 'login' }
});
// Step 2: Poll and retrieve magic link
const otpData = await pollForOTP(request, email);
console.log('Magic Link:', otpData.magicLink);
// Step 3: Navigate to magic link (auto-login!)
await page.goto(otpData.magicLink);
// Step 4: Verify logged in (no OTP needed!)
await expect(page.locator('[data-testid="dashboard"]')).toBeVisible();
});🌲Cypress (JavaScript)
describe('OTP Verification', () => {
const BASE_URL = 'https://www.passthenote.com';
const email = 'testuser@passthenote.com';
// Helper: Recursive polling for delayed emails
const pollForOTP = (email, maxAttempts = 10) => {
if (maxAttempts === 0) {
throw new Error('Polling timeout - email not received');
}
return cy.request({
url: `${BASE_URL}/api/gateway/otp/inbox/${encodeURIComponent(email)}`,
failOnStatusCode: false
}).then((response) => {
if (response.status === 202) {
cy.log(`Email delayed, retrying... (${11 - maxAttempts}/10)`);
cy.wait(1000);
return pollForOTP(email, maxAttempts - 1);
}
if (response.status === 200) {
// Check spam folder
if (response.body.folder === 'SPAM') {
cy.log('⚠️ Warning: Email in spam folder!');
}
return cy.wrap(response.body);
}
throw new Error(`Unexpected status: ${response.status}`);
});
};
it('should complete OTP verification with polling', () => {
// Step 1: Request OTP via UI (with delay)
cy.visit(`${BASE_URL}/app/login?testDelay=true`);
cy.get('[data-testid="email-input"]').type(email);
cy.get('[data-testid="request-otp"]').click();
cy.get('[data-testid="otp-sent-message"]').should('be.visible');
// Step 2: Poll for OTP (handles 202 response)
pollForOTP(email).then((otpData) => {
cy.log(`Retrieved OTP: ${otpData.otp}`);
// Step 3: Enter OTP in UI
cy.get('[data-testid="otp-input"]').type(otpData.otp);
cy.get('[data-testid="verify-button"]').click();
// Step 4: Verify success
cy.get('[data-testid="success-message"]').should('be.visible');
});
});
it('Magic Link - extract and navigate (Interview Question)', () => {
// Step 1: Send OTP with magic link
cy.request('POST', `${BASE_URL}/api/gateway/otp/send`, {
email,
purpose: 'login'
});
// Step 2: Poll and get magic link
pollForOTP(email).then((otpData) => {
cy.log(`Magic Link: ${otpData.magicLink}`);
// Step 3: Extract token and navigate
const url = new URL(otpData.magicLink);
const token = url.searchParams.get('token');
cy.log(`Extracted Token: ${token}`);
// Step 4: Visit magic link (auto-login!)
cy.visit(otpData.magicLink);
cy.get('[data-testid="dashboard"]').should('be.visible');
});
});
it('should detect spam folder', () => {
// Send to spam
cy.request('POST', `${BASE_URL}/api/gateway/otp/send`, {
email,
purpose: 'login',
triggerSpam: true
});
// Retrieve and check folder
cy.request(`${BASE_URL}/api/gateway/otp/inbox/${encodeURIComponent(email)}`)
.then((response) => {
expect(response.body.folder).to.eq('SPAM');
cy.log('✅ Test passed: Detected spam folder!');
});
});
});☕REST Assured (Java)
import io.restassured.RestAssured;
import io.restassured.response.Response;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
public class OTPApiTest {
private static final String BASE_URL = "https://www.passthenote.com/api/gateway";
@BeforeClass
public static void setup() {
RestAssured.baseURI = BASE_URL;
}
@Test
public void testCompleteOTPFlow() {
String email = "testuser@passthenote.com";
// Step 1: Send OTP
given()
.contentType("application/json")
.body("{ \"email\": \"" + email + "\", \"purpose\": \"login\" }")
.when()
.post("/otp/send")
.then()
.statusCode(200)
.body("message", equalTo("OTP sent successfully"));
// Step 2: Retrieve OTP from inbox
Response otpResponse = given()
.when()
.get("/otp/inbox/" + email)
.then()
.statusCode(200)
.body("email", equalTo(email))
.body("otp", matchesPattern("\\d{6}"))
.extract().response();
String otp = otpResponse.path("otp");
System.out.println("Retrieved OTP: " + otp);
// Step 3: Verify OTP
given()
.contentType("application/json")
.body("{ \"email\": \"" + email + "\", \"otp\": \"" + otp + "\" }")
.when()
.post("/otp/verify")
.then()
.statusCode(200)
.body("verified", equalTo(true))
.body("message", equalTo("OTP verified successfully"));
}
@Test
public void testInvalidOTP() {
String email = "testuser@passthenote.com";
given()
.contentType("application/json")
.body("{ \"email\": \"" + email + "\", \"otp\": \"000000\" }")
.when()
.post("/otp/verify")
.then()
.statusCode(401)
.body("verified", equalTo(false))
.body("error", equalTo("Invalid OTP"));
}
}💡 Best Practices for OTP Automation
✅ Do's
- • Wait for OTP to be generated before retrieving (2-3 second delay)
- • Check OTP expiration time before using
- • Handle OTP_NOT_FOUND and OTP_EXPIRED errors
- • Verify one-time use (second verification should fail)
- • Test invalid OTP scenarios
- • Use unique emails per test run when possible
- • Clear OTPs after test completion
❌ Don'ts
- • Don't hardcode OTP values in tests
- • Don't skip wait time between send and retrieve
- • Don't reuse expired OTPs
- • Don't assume OTP format (always retrieve via API)
- • Don't run tests in parallel with same email
- • Don't forget to test error scenarios
- • Don't skip verification response assertions
🎯 Real-World Application:
In production, you would integrate with email testing services like Mailtrap, MailHog, or use email forwarding to a test inbox. This mock inbox simulates that pattern, teaching you the essential skill of automating 2FA/MFA flows that are critical in modern applications.