automated email testing

Test transactional email in WebdriverIO without fixed sleeps

Use a receive-only inbox and a wait-for-message request to verify invoice and account-change emails in WebdriverIO without guessing how long delivery will take.

By Wesley Copley·October 22, 2026·3 min read
What matters here
  1. InboxRhino’s wait-for-message endpoint can wait up to 180 seconds for a matching email.
  2. A subject filter narrows the message returned for an asynchronous notification test.
  3. Email assertions should check a stable identifier in the message, not only that an email arrived.

Transactional email often arrives after the application has finished its visible work. An invoice alert or account-change notice may be queued and delivered a few seconds later, or take longer under CI load. A fixed sleep in a WebdriverIO test guesses at that delay: set it too short and the test flakes; set it too long and every run pays the cost.

A more reliable pattern is to trigger the application action, then ask an inbox API to wait for the expected message. InboxRhino provides receive-only inboxes and an HTTP wait-for-message endpoint that waits up to 180 seconds. The test can wait on mail rather than pausing for an arbitrary interval.

Give the test a dedicated recipient

Start by creating an InboxRhino inbox through its API. The service uses real MX addresses that receive mail through Cloudflare. Use the address returned by the create-inbox response as the recipient for the account or transaction under test. Keep the inbox ID for the later message request.

The documented create call uses a bearer API key:

const created = await fetch('https://api.inboxrhino.in/v1/inboxes', { method: 'POST', headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' } }).then((r) => r.json());

InboxRhino’s example response exposes the inbox ID at created.data.id. Use the address supplied by the API when configuring the test user or transaction. Avoid sharing one recipient across parallel cases if the same subject could make a message ambiguous.

Trigger the event, then wait for its email

In the WebdriverIO test, perform the normal UI action first: for example, request an invoice notification or change an account setting. Then make a REST request for the message. The endpoint accepts a wait duration and can filter by subject or sender. A subject phrase that is stable across test runs is a useful first filter.

const mail = await fetch(`https://api.inboxrhino.in/v1/inboxes/${created.data.id}/messages?wait_seconds=180&limit=1&include=content&subject=${encodeURIComponent('Invoice')}`, { headers: { Authorization: `Bearer ${key}` } }).then((r) => r.json());

This uses the wait-for-message request instead of a timer such as browser.pause(10000). The request waits for a matching message, for up to the specified limit. It does not make email delivery synchronous; it gives the test a bounded wait for an asynchronous event. If the response contains no matching message, treat that as a failed notification check and include the response or relevant test context in the failure output.

Assert on the message that matters

First check that a message was returned and that its subject is the expected one. InboxRhino’s example reads the first result as mail.data[0].subject. For a stronger transactional check, include a stable value in the assertion: an invoice number, the account identifier, or the new setting. The endpoint request includes content, and the API returns a parsed body; use the response field documented in the API reference for the body assertion.

Do not make the test depend on incidental presentation details such as whitespace, a full HTML snapshot, or a timestamp that changes on every run. A subject filter helps select the message; a content assertion checks that it belongs to this transaction. If multiple notifications can share a subject, use a sender filter where appropriate and assert a transaction-specific value in the message.

Keep the WebdriverIO check bounded

  • Use a separate inbox for the flow or test data that needs isolation.
  • Trigger the event through the application before requesting the message.
  • Use the longest wait the scenario reasonably needs, up to the API’s 180-second limit, rather than adding repeated fixed sleeps.
  • Assert both that the expected notification arrived and that its content identifies the event.
  • Keep the API key in the test environment, not in committed test code.

InboxRhino inboxes are receive-only: they cannot send, reply to, or forward email. Messages, headers, and attachments are deleted after 30 days, so the inbox is for test delivery and inspection, not a permanent record. The free tier includes 11 active inboxes and 33 emails per month; account for those limits when choosing how often CI runs this flow.

This approach works for other email checks too: the application action remains a WebdriverIO interaction, while a REST request handles the asynchronous notification. For background on how receiving infrastructure, HTML inspection, and quotas affect test design, see the practical choices behind automated email testing.

More from InboxRhino News