Test an Email OTP from Signup to Confirmed Account
Receive a real verification email in an isolated inbox, extract its current OTP, and use Cypress to confirm the intended account.
Use with your tools

Testing an email OTP means completing the action that the code enables. The application sends a verification email, the test reads the code, and the application must accept it for the intended account. Finding six digits in a message is only the middle of that journey.
Manual copying becomes slow when every signup has a different address and code. Parallel tests make a shared mailbox especially awkward: the latest message may belong to another user or an earlier resend. MailSlurp gives each test a private receiving inbox and APIs for waiting on its message and extracting the code.
This walkthrough uses Cypress to drive your application's signup and verification screens. For the broader policy checks, including SMS and authenticator codes, use the OTP testing plan.
Decide what successful verification means
An email OTP is a short-lived code delivered to an address. A magic link is a URL that performs an action when opened. They need different extraction steps, even when both lead to the same account screen.
A code used on its own can be a passwordless login or address-confirmation flow. It is not automatically MFA. If your application requires a password and an independent second factor, test both stages and check that protected access remains blocked until the required challenge succeeds.
For this example, assume your application has /signup, an email field, a Create account button, a code field and a Verify code button. After confirmation, an element with data-testid="signed-in-email" shows the authenticated account's email. Replace these selectors and the expected subject with your own application's contract.
Connect Cypress to the receiving inbox
Install cypress and mailslurp-client. Configure the Node tasks from the Cypress OTP setup in cypress.config.cjs, and set MAILSLURP_API_KEY in the Node environment. This keeps the API key out of the browser test.
Those tasks expose three operations used below:
createTestInboxreturns an isolated inbox with a ten-minute expiry.receiveOtpwaits for the expected subject in that inbox after the recorded request time, checks the recipient, then extracts one unambiguous six-digit code.deleteTestInboxdeletes only the inbox ID supplied by this test.
Set CYPRESS_BASE_URL to your development or staging application's origin. The example is a recipe for your application, rather than a claim that every signup uses these selectors or automatically signs the user in after confirmation.
Keep signup and confirmation in one test
Save the following as cypress/e2e/email-otp.cy.js. A separate inbox is created for each scenario, and teardown runs after a failed assertion as well as success. The code does not depend on another it block having already registered a user.
describe("email OTP verification", () => {
let inbox;
beforeEach(() => {
inbox = undefined;
cy.task("createTestInbox", null, { log: false }).then(value => {
inbox = value;
});
});
afterEach(() => {
if (inbox) cy.task("deleteTestInbox", inbox.id, { log: false });
});
it("confirms the account that requested the code", () => {
cy.visit("/signup");
cy.get('input[name="email"]').type(inbox.emailAddress, { log: false });
cy.then(() => {
const since = new Date().toISOString();
cy.contains("button", "Create account").click();
return cy.task("receiveOtp", {
inboxId: inbox.id,
address: inbox.emailAddress,
since,
subject: "Verify your email",
}, { timeout: 90_000, log: false });
}).then(code => {
cy.get('input[name="code"]').type(code, { log: false });
cy.contains("button", "Verify code").click();
cy.get('[data-testid="signed-in-email"]')
.should("have.text", inbox.emailAddress);
});
});
});
The time boundary is captured immediately before requesting the email. The Node task uses a 60-second message wait; the 90-second Cypress task budget also allows code extraction to finish. Keep unreadOnly: false in the task so opening the message during debugging does not change which email the test can read.
If your application sends several verification messages with the same subject, add the request's own identifier to the match. A fresh inbox isolates users; it does not distinguish two resends to the same user.
Stop when extraction is missing or ambiguous
The receiveOtp task rejects a response without one usable code. Keep this check even if a simple regex appears to work with today's template. Emails can contain order numbers, dates and support numbers alongside the OTP.
Treat the code as text. Converting 004281 to a number removes its leading zeros. If your template uses a different length or alphanumeric codes, change the extraction rule to match the application's format, then check both a valid example and an ambiguous message. The code extraction API explains the result fields and template options.
Do not retry different numbers against the application's form until one works. Fail the extraction step and inspect the relevant message with appropriate access controls.
Check the account after confirmation
The sample assumes successful confirmation signs the user in. If your application instead returns to a login form, sign in there and assert the resulting account identity. If it only sets a verified flag, inspect that state through the supported account page or API.
Use a clean browser session for this scenario. An account that was already authenticated can make a final account screen pass even when verification failed. Confirm that protected access is unavailable before submitting the code and available only after the required application steps.
Add separate expiry and resend scenarios
Once the happy path works, give each policy test its own account and inbox:
- Submit a deliberately invalid code and verify that access remains blocked.
- Submit a code outside the application's configured lifetime and verify rejection.
- Request a resend and check which of the old and new codes the application accepts.
- Attempt to reuse a consumed code and check the application's one-time-use policy.
- Exercise the attempt limit with a dedicated user so lockout does not affect other tests.
Use a controlled application clock or short test-environment lifetime for expiry when supported. Keep real-message delivery in the integration test and policy-only checks in faster local tests where appropriate. Avoid logging usable codes or full email bodies, and clean up application test users separately from their MailSlurp inboxes.