DEV Community

Abdelrhafar Naouri
Abdelrhafar Naouri

Posted on

How to send OTP SMS in Morocco with Node.js

How to Send OTP SMS in Morocco with Node.js

Building user verification for applications in Morocco requires handling local carrier formats and ensuring fast message delivery. Moroccan operators—Maroc Telecom (IAM), Orange Morocco, and Inwi—enforce strict E.164 phone formatting and delivery filters.

This guide covers setting up a two-step One-Time Password (OTP) workflow in Node.js using EnvoiSMS.ma.


Prerequisites

Install the official SDK package:

npm install envoisms
Enter fullscreen mode Exit fullscreen mode

Step 1: Normalizing Moroccan Phone Numbers

Moroccan mobile numbers use the international prefix +212 followed by a 9-digit subscriber number starting with 6 or 7. Local users often input numbers starting with 06 or 07.

Always normalize input before sending API requests:

function normalizeMoroccanPhone(input: string): string {
  const cleaned = input.replace(/\D/g, '');

  if (cleaned.startsWith('212')) {
    return `+${cleaned}`;
  }
  if (cleaned.startsWith('0')) {
    return `+212${cleaned.slice(1)}`;
  }
  return `+212${cleaned}`;
}

// Example outputs:
// "0661234567"  => "+212661234567"
// "212770123456" => "+212770123456"
Enter fullscreen mode Exit fullscreen mode

Step 2: Requesting an OTP Code

Initialize the client and invoke sendOtp(). The API generates a cryptographically secure OTP, hashes it server-side, and dispatches the SMS via direct local operator links.

import { EnvoiSMSClient } from 'envoisms';

const client = new EnvoiSMSClient(process.env.ENVOISMS_API_KEY!);

async function requestVerification(rawPhoneNumber: string) {
  const phone = normalizeMoroccanPhone(rawPhoneNumber);

  try {
    const response = await client.sendOtp({
      to: phone,
      brand: 'MyCompany',
      code_length: 6,
      expiry: 600, // Code expires in 10 minutes
    });

    // Save response.session_id in the user's session or temp auth state
    return {
      success: true,
      sessionId: response.session_id,
      expiresAt: response.expires_at,
    };
  } catch (error: any) {
    console.error('OTP request failed:', error.message);
    throw new Error(`Failed to send verification code: ${error.message}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Verifying the Code Submitted by the User

When the user enters the numeric code from their phone, send it along with the session_id to checkOtp():

async function verifyCode(sessionId: string, userCode: string) {
  try {
    const response = await client.checkOtp({
      session_id: sessionId,
      code: userCode.trim(),
    });

    if (response.verified) {
      // OTP is valid. Clear the temporary session ID and mark user as verified.
      return { verified: true };
    }

    return { verified: false, message: 'Invalid verification code.' };
  } catch (error: any) {
    if (error.message.includes('MAX_ATTEMPTS')) {
      return { verified: false, message: 'Maximum verification attempts exceeded. Please request a new code.' };
    }
    if (error.message.includes('SESSION_NOT_FOUND')) {
      return { verified: false, message: 'Verification session expired. Please request a new code.' };
    }

    return { verified: false, message: error.message };
  }
}
Enter fullscreen mode Exit fullscreen mode

Direct HTTP Request Alternative (Native Fetch)

If you prefer not using an external SDK dependency, implement the API calls with standard fetch:

const API_BASE = 'https://api.envoisms.ma/v1';
const API_KEY = process.env.ENVOISMS_API_KEY!;

// 1. Send OTP
const sendRes = await fetch(`${API_BASE}/verify/send`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${API_KEY}`,
  },
  body: JSON.stringify({
    to: '+212661234567',
    brand: 'MyCompany',
    code_length: 6,
    expiry: 600,
  }),
});

const sendData = await sendRes.json();
const sessionId = sendData.session_id;

// 2. Check OTP
const checkRes = await fetch(`${API_BASE}/verify/check`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${API_KEY}`,
  },
  body: JSON.stringify({
    session_id: sessionId,
    code: '492018',
  }),
});

const checkData = await checkRes.json();
console.log('Verified:', checkData.verified);
Enter fullscreen mode Exit fullscreen mode

Production Security Best Practices

  1. Rate Limit Requests: Restrict OTP requests per IP and phone number (e.g., maximum 3 requests per 15 minutes) to prevent SMS pumping attacks.
  2. Never Expose API Keys: Keep ENVOISMS_API_KEY strictly on the backend server.
  3. Session Bound: Associate the returned session_id with the authenticated user's temporary state or JWT token rather than trusting raw client payload parameters.

Resources

Top comments (0)