Автоматизация извлечения OTP из писем в CI/CD-пайплайнах
If your application sends OTP codes via email — for registration, password reset, or two-factor authentication — you've probably run into this problem: your automated test suite gets stuck waiting for a human to open an inbox, copy a 6-digit code, and paste it into a form field. That single manual step breaks the entire promise of a fully automated CI/CD pipeline.
TOTP authenticator apps like Google Authenticator solve a different problem: they generate time-based codes for app-based 2FA. They don't help you intercept a one-time code delivered to an actual email address. SMS OTP is even harder — you'd need a SIM card, a carrier API, or specialized hardware. Email OTP sits in a sweet spot: it can be reliably automated with the right API.
This guide shows you exactly how to do it using GridInbox, with real working code in JavaScript/Node.js, Python, and GitHub Actions YAML.
According to NIST Special Publication 800-63B, SMS-based OTP delivery has a known interception risk — but email OTP, when delivered to a private, API-controlled inbox, significantly reduces man-in-the-middle exposure in automated test environments.
Source: NIST SP 800-63B, Digital Identity Guidelines (2024 revision)
1. Как работает извлечение OTP из электронной почты
There are two architectural approaches to getting OTP codes out of emails programmatically:
Опрос API
Your test code periodically calls the mailbox API to check for new messages. If no OTP-containing email has arrived yet, it waits a short interval and tries again — up to a configurable maximum number of attempts. This is the simplest approach for CI/CD because it requires no public endpoint: your runner just makes outbound HTTP calls.
Webhook-уведомление
The mailbox service notifies your endpoint the moment a new email arrives. This is faster and more efficient at scale, but requires your test infrastructure to expose a publicly reachable HTTPS endpoint — which is impractical in many CI environments (ephemeral containers, firewalled runners, etc.).
For most CI/CD use cases, polling wins. A 2-second polling interval with a 20-second timeout is fast enough to feel instant and cheap enough not to matter in terms of API credits.
Задача парсинга OTP
OTP codes appear in emails in many different formats:
- Plain digits:
847291 - Hyphenated:
847-291 - Bolded in HTML:
<strong>847291</strong> - In the subject line:
"Your code is 847291" - Surrounded by whitespace or punctuation in a paragraph
Writing robust regex to handle all these cases across dozens of senders is tedious and error-prone. GridInbox handles this extraction server-side using multiple pattern strategies — so your code just reads a clean otp field from the JSON response.
2. Пример ответа API GridInbox
When you query a mailbox for its latest messages, GridInbox returns a structured JSON response. The otp field is automatically populated whenever the server detects a numeric verification code in the email:
{
"id": "msg_abc123",
"from": "[email protected]",
"subject": "Your verification code is 847291",
"otp": "847291",
"received_at": "2026-04-01T10:23:45Z",
"text_body": "Your verification code is 847291. It expires in 10 minutes.",
"html_body": "<p>Your verification code is <strong>847291</strong></p>"
}
No regex needed on your end. The otp field is null if the email doesn't contain a detectable OTP — making it trivial to filter messages down to the one you care about. You also get text_body and html_body if you need to perform additional assertions on the email content itself.
Pro tip: Use a dedicated mailbox per test environment (e.g.,[email protected]vs[email protected]) so OTPs from different environments never collide.
3. Полный пример на JavaScript / Node.js
The following helper function polls a GridInbox mailbox until an OTP-containing message appears or the maximum number of attempts is exceeded. Drop this into your test utilities and import it wherever you need OTP extraction:
// otp-helper.js
const API_BASE = 'https://api.gridinbox.com/api/v1';
const API_KEY = process.env.GRIDINBOX_API_KEY;
async function waitForOtp(mailboxId, { maxAttempts = 10, delayMs = 2000 } = {}) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
console.log(`Polling attempt ${attempt}/${maxAttempts}...`);
const res = await fetch(`${API_BASE}/mailboxes/${mailboxId}/messages?limit=1`, {
headers: { 'X-API-Key': API_KEY }
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
const { messages } = await res.json();
if (messages && messages.length > 0 && messages[0].otp) {
console.log(`OTP found: ${messages[0].otp}`);
return messages[0].otp;
}
if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
throw new Error(`OTP not found after ${maxAttempts} attempts`);
}
module.exports = { waitForOtp };
Usage inside a Playwright or Puppeteer test looks like this:
const { waitForOtp } = require('./otp-helper');
test('user can complete OTP verification', async ({ page }) => {
// Trigger the OTP email (e.g., submit the registration form)
await page.fill('#email', '[email protected]');
await page.click('#send-otp');
// Wait for the OTP to arrive in the mailbox
const otp = await waitForOtp('mbx_your_mailbox_id', { maxAttempts: 15, delayMs: 2000 });
// Enter it into the form
await page.fill('#otp-input', otp);
await page.click('#verify');
await expect(page.locator('#success-message')).toBeVisible();
});
This test runs entirely headlessly in CI with zero human involvement. The waitForOtp call typically resolves within 2–6 seconds, well within normal test timeout budgets.
4. Пример на Python
For pytest, Selenium, or any Python-based automation framework, the equivalent helper using the requests library:
# otp_helper.py
import time
import requests
import os
API_BASE = 'https://api.gridinbox.com/api/v1'
API_KEY = os.environ['GRIDINBOX_API_KEY']
def wait_for_otp(mailbox_id, max_attempts=10, delay_seconds=2):
"""Poll mailbox until OTP is found or timeout."""
headers = {'X-API-Key': API_KEY}
for attempt in range(1, max_attempts + 1):
print(f'Polling attempt {attempt}/{max_attempts}...')
response = requests.get(
f'{API_BASE}/mailboxes/{mailbox_id}/messages',
headers=headers,
params={'limit': 1}
)
response.raise_for_status()
data = response.json()
messages = data.get('messages', [])
if messages and messages[0].get('otp'):
otp = messages[0]['otp']
print(f'OTP found: {otp}')
return otp
if attempt < max_attempts:
time.sleep(delay_seconds)
raise TimeoutError(f'OTP not found after {max_attempts} attempts')
And the corresponding pytest test:
# test_registration.py
from selenium.webdriver.common.by import By
from otp_helper import wait_for_otp
def test_otp_verification(driver):
driver.get('https://yourapp.com/register')
driver.find_element(By.ID, 'email').send_keys('[email protected]')
driver.find_element(By.ID, 'send-otp').click()
# Fetch the OTP from the mailbox API
otp = wait_for_otp('mbx_your_mailbox_id', max_attempts=15, delay_seconds=2)
driver.find_element(By.ID, 'otp-input').send_keys(otp)
driver.find_element(By.ID, 'verify').click()
assert 'Welcome' in driver.page_source
GitHub's 2024 State of the Octoverse report found that the average CI/CD pipeline runs 217 automated tests per push — yet fewer than 30% include any form of email or notification flow verification.
Source: GitHub, State of the Octoverse 2024
This gap is where authentication regressions hide until they reach production.
5. Интеграция с GitHub Actions
Store your GridInbox API key as a repository secret (GRIDINBOX_API_KEY), then reference it in your workflow. Here's a complete example for a Node.js project running Playwright end-to-end tests:
# .github/workflows/e2e.yml
name: E2E Tests with OTP Automation
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
env:
# Inject the API key from GitHub secrets
GRIDINBOX_API_KEY: ${{ secrets.GRIDINBOX_API_KEY }}
# Point tests at the staging environment
APP_URL: https://staging.yourapp.com
# The dedicated staging test mailbox
TEST_MAILBOX_ID: ${{ secrets.TEST_MAILBOX_ID }}
run: npx playwright test --reporter=html
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
A few best practices to keep this reliable in production CI:
- Use a dedicated test mailbox per environment. Never share a mailbox between staging and production tests — you'll pick up the wrong OTP.
- Filter by
received_at. When polling, only accept messages received after your test started. Add asincetimestamp parameter to the API call to avoid picking up stale OTPs from previous test runs. - Set a generous timeout. Email delivery in staging environments can be slow. A 30-second timeout with 2-second polling intervals is a safe default that still finishes well within your job timeout.
- Rotate API keys periodically. Treat
GRIDINBOX_API_KEYlike any other secret — rotate it on a schedule and audit access in your GridInbox dashboard.
"Manual OTP retrieval during test runs is the single biggest bottleneck in authentication test automation. Once you replace it with a mailbox API, your test suite becomes reproducible — and your CI pipelines stop lying to you."
Заключение
Manual OTP copy-pasting is a silent CI/CD killer. It seems minor until your team starts shipping faster and that one manual step becomes a daily bottleneck — or worse, a source of flaky test results because a human forgot to check the inbox in time.
With a mailbox API that exposes parsed OTP fields, you can close this gap entirely. Your pipeline triggers the email, polls for the code, types it into the form, and completes the verification — all in the time it would have taken you to unlock your phone.
GridInbox provides dedicated mailboxes with server-side OTP parsing, a clean REST API, and per-tenant isolation — everything you need to make OTP automation a one-afternoon implementation rather than a multi-week infrastructure project.
Прекратите копировать и вставлять OTP-коды
GridInbox предоставляет вашему CI/CD-пайплайну реальный почтовый ящик с серверным разбором OTP. Настройка за минуты, работает с любым тестовым фреймворком, нулевые накладные расходы на инфраструктуру.
Get Started for Free →