
Comprehensive EmailJS Integration Guide
Master client-side email dispatching, serverless architecture, and reCAPTCHA spam controls.
Read Documentation →
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.
An enterprise-grade email verification engine must incorporate four foundational security controls to guarantee token integrity and protect application resources:
A resilient verification workflow progresses through four distinct lifecycle stages:
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;
}
Before launching an email OTP verification workflow in production, review these crucial security and operational safeguards: