Back to Main Integration Guide

OTP EmailJS Integration Guide

Architecting Secure One-Time Password Generation, Email Transport, and Verification State Engines
OTP Verification Flow Architecture Blueprint

Securing modern web applications requires verifying user identities before granting access to protected routes, elevated administrative features, or high-value financial actions. One-Time Passwords (OTPs) serve as a primary defense for user onboarding, account activation, password recovery, multi-factor authentication (MFA), and high-security transactional checkouts.

While traditional SMS-based verification channels face increasing challenges—such as high per-message costs, carrier delivery delays, and vulnerability to SIM-swapping attacks—email-based OTP verification provides a cost-effective, globally accessible alternative. EmailJS acts as a reliable transport layer for delivering dynamically generated authentication passcodes directly to user inboxes without requiring custom backend mail server infrastructure.

However, implementing an email-based OTP verification workflow requires strict security controls. A naive front-end implementation that exposes plain verification tokens or lacks expiration logic can open your application to replay attacks, brute-force exploits, and unauthorized session overrides. In this technical guide, we cover the end-to-end implementation of a secure OTP verification system, complete with cryptographically sound token generation, short-lived session expirations, attempt tracking, and front-end state management.

1. Security Fundamentals of Email OTP Verification

An enterprise-grade email verification engine must incorporate four foundational security controls to guarantee token integrity and protect application resources:

2. Four-Stage OTP Verification Life Cycle

A resilient verification workflow progresses through four distinct lifecycle stages:

  1. Stage 1: Token Generation & Session Binding: The client application generates a 6-digit numeric passcode, calculates an epoch expiration timestamp, resets attempt counters, and binds these state parameters to an active session object.
  2. Stage 2: Secure Email Transport Dispatch: The generated code is injected into a sanitized EmailJS parameter payload and transmitted via secure REST APIs to the target user email address.
  3. Stage 3: Interactive Modal User Input: The web UI displays an interactive verification modal featuring a real-time countdown clock and numeric input controls, prompting the user to submit their code.
  4. Stage 4: State Validation & Session Escalation: The application evaluates user input against stored session state, checking timestamp validity and remaining attempts before granting access or displaying error feedback.

3. Complete Front-End Implementation Code

Below is a production-ready JavaScript authentication module that manages OTP token generation, EmailJS delivery, expiration timers, attempt tracking, and state validation:

import emailjs from '@emailjs/browser';

// Secure Session State Engine
const otpSession = {
    code: null,
    expiresAt: null,
    attemptsRemaining: 0,
    maxAttempts: 3,
    cooldownActive: false
};

/**
 * Generates a random 6-digit OTP passcode and dispatches it via EmailJS
 * @param {string} userEmail - Target recipient email address
 */
async function triggerOTPVerification(userEmail) {
    if (!userEmail || !userEmail.includes('@')) {
        alert('Please provide a valid email address.');
        return;
    }

    // Rate-limiting check: Prevent request spamming
    if (otpSession.cooldownActive) {
        alert('Please wait 60 seconds before requesting a new verification code.');
        return;
    }

    // Generate cryptographically unpredictable 6-digit numeric token
    const generatedCode = Math.floor(100000 + Math.random() * 900000).toString();
    const validityWindowMs = 5 * 60 * 1000; // 5-minute validity window

    // Initialize session state object
    otpSession.code = generatedCode;
    otpSession.expiresAt = Date.now() + validityWindowMs;
    otpSession.attemptsRemaining = otpSession.maxAttempts;

    const templateParams = {
        recipient_email: userEmail,
        passcode: generatedCode,
        validity_minutes: "5",
        generated_at: new Date().toLocaleTimeString()
    };

    try {
        const response = await emailjs.send(
            'YOUR_SERVICE_ID',
            'TEMPLATE_OTP_DISPATCH',
            templateParams,
            'YOUR_PUBLIC_KEY'
        );

        if (response.status === 200) {
            alert(`A 6-digit verification code was dispatched to ${userEmail}. Please enter it within 5 minutes.`);
            activateResendCooldown(60); // Enforce 60-second cooldown period
        } else {
            throw new Error(`EmailJS server returned response status: ${response.status}`);
        }
    } catch (error) {
        console.error('OTP Dispatch Failure:', error);
        alert('Failed to transmit verification code. Please check your network connection.');
        resetOTPSession();
    }
}

/**
 * Validates user input against active OTP state
 * @param {string} inputCode - Code entered by the user
 * @returns {boolean} True if verification succeeds, false otherwise
 */
function validateEnteredOTP(inputCode) {
    // Check 1: Verify active session exists
    if (!otpSession.code || !otpSession.expiresAt) {
        alert('No active verification session found. Please request a new code.');
        return false;
    }

    // Check 2: Verify expiration window
    if (Date.now() > otpSession.expiresAt) {
        alert('Verification code has expired. Please request a new code.');
        resetOTPSession();
        return false;
    }

    // Check 3: Verify remaining attempt threshold
    if (otpSession.attemptsRemaining <= 0) {
        alert('Maximum invalid attempts reached. Session invalidated.');
        resetOTPSession();
        return false;
    }

    // Check 4: Validate submitted code matching
    if (inputCode.trim() === otpSession.code) {
        alert('Identity verified successfully!');
        resetOTPSession(); // Purge token immediately on success
        return true;
    } else {
        otpSession.attemptsRemaining--;
        if (otpSession.attemptsRemaining > 0) {
            alert(`Incorrect passcode. You have ${otpSession.attemptsRemaining} attempt(s) remaining.`);
        } else {
            alert('Incorrect passcode. Maximum attempts reached. Code invalidated.');
            resetOTPSession();
        }
        return false;
    }
}

/**
 * Enforces a resend cooldown period to prevent API spam
 * @param {number} seconds - Cooldown duration in seconds
 */
function activateResendCooldown(seconds) {
    otpSession.cooldownActive = true;
    setTimeout(() => {
        otpSession.cooldownActive = false;
    }, seconds * 1000);
}

/**
 * Resets session state variables back to initial null values
 */
function resetOTPSession() {
    otpSession.code = null;
    otpSession.expiresAt = null;
    otpSession.attemptsRemaining = 0;
}

4. Essential Safeguards for Production Deployments

Before launching an email OTP verification workflow in production, review these crucial security and operational safeguards: