OTP Provider Configuration
This guide explains how to configure email and SMS providers for sending One-Time Password (OTP) codes in Unisync.
Quick Reference
Looking for a quick setup? Check the Provider Quick Reference for condensed instructions.
Overview
Unisync supports OTP delivery via:
- SMS Providers: For phone number verification
- Email Providers: For email verification (Coming Soon)
Current Implementation Status
SMS Providers ✅
SMS OTP delivery is fully implemented and configured through the database (settings_config table). Currently supported:
- BulkSMS Bangladesh (
bulksmsbd)
Email Providers ⏳
Email OTP delivery is planned but not yet implemented. The configuration exists in environment variables, but the actual email sending functionality needs to be implemented.
SMS Provider Configuration
Architecture
SMS providers are configured through the database rather than environment variables. This provides:
- Dynamic configuration without server restart
- Support for multiple providers
- Template customization
- Runtime provider switching
BulkSMS Bangladesh Setup
Step 1: Set Environment Variables
Add the following to your server/.env file:
# BulkSMS Bangladesh Configuration
BULK_SMS_API_KEY=your_api_key_here
BULK_SMS_SECRET_KEY=your_secret_key_here
BULK_SMS_CALLER_ID=your_sender_idTo obtain these credentials:
- Visit BulkSMSBD.net
- Create an account
- Purchase SMS credits
- Get your API credentials from the dashboard
Step 2: Database Configuration
The SMS provider configuration is stored in the settings.config table with identifier OTP_PROVIDER. This is automatically set up during database migrations.
Default Configuration:
{
"provider": {
"identifier": "bulksmsbd",
"website": "https://bulksmsbd.net",
"endpoint": {
"url": "http://118.67.213.114:3775/sendtext",
"method": "GET",
"query": [
{
"key": "apikey",
"value": { "name": "BULK_SMS_API_KEY", "type": "ENV_VARIABLE" }
},
{
"key": "secretkey",
"value": { "name": "BULK_SMS_SECRET_KEY", "type": "ENV_VARIABLE" }
},
{
"key": "callerID",
"value": { "name": "BULK_SMS_CALLER_ID", "type": "ENV_VARIABLE" }
},
{
"key": "toUser",
"value": { "type": "TEMPLATE_VARIABLE", "value": "{{number}}" }
},
{
"key": "messageContent",
"value": { "type": "TEMPLATE_VARIABLE", "value": "{{message}}" }
}
],
"headers": {
"Content-Type": "application/json"
},
"variables": {
"env": [
"BULK_SMS_API_KEY",
"BULK_SMS_SECRET_KEY",
"BULK_SMS_CALLER_ID"
],
"template": ["number", "message"]
}
}
},
"template": {
"otp": {
"message": "Your OTP is {{otp}}",
"variables": ["otp"]
}
},
"providerToUse": "bulksmsbd"
}Step 3: Verify Configuration
To verify your SMS provider is configured correctly:
Start your server:
bashcd server yarn devSend a test OTP:
bashcurl -X POST http://localhost:9201/api/v1/otp/send \ -H "Content-Type: application/json" \ -d '{ "input": { "identifier": "+8801712345678", "identifierType": "PHONE", "purpose": "LOGIN" } }'Check the server logs for:
Initializing SMS provider: bulksmsbd SMS sent successfully via BulkSMSBD
Message Templates
The default SMS messages for different purposes are:
- Login:
Your UniSync login verification code is {OTP}. Valid for 5 minutes. Do not share this code with anyone. - Signup:
Your UniSync signup verification code is {OTP}. Valid for 5 minutes. Do not share this code with anyone. - Password Reset:
Your UniSync password reset code is {OTP}. Valid for 5 minutes. Do not share this code with anyone.
These templates are defined in the provider implementation and can be customized by modifying:
server/src/lib/providers/sms/bulksmsbd.tsEmail Provider Configuration (Coming Soon)
Planned Support
Email OTP delivery is planned with support for:
- Console (
console): Log to console (development only) - Resend (
resend): Resend email service - SendGrid (
sendgrid): SendGrid email service - AWS SES (
ses): Amazon Simple Email Service
Environment Variables (Not Yet Active)
The following environment variables are defined but not yet used:
# Email Provider (console, resend, sendgrid, ses)
EMAIL_PROVIDER=console
RESEND_API_KEY=
SENDGRID_API_KEY=
SENDGRID_FROM_EMAIL=noreply@yourdomain.com
AWS_SES_REGION=
AWS_SES_FROM_EMAIL=Current Email OTP Behavior
When you send an OTP with identifierType: "EMAIL", the system will:
- Create the OTP record in the database ✅
- Hash and store the OTP securely ✅
- Skip sending the email ⚠️
The OTP verification will work, but users won't receive the email. This is suitable for testing but requires implementation for production use.
Adding New SMS Providers
Step 1: Create Provider Implementation
Create a new file in server/src/lib/providers/sms/:
// server/src/lib/providers/sms/twilio.ts
import { SMSProvider } from "../interface";
import { config } from "../../../config/environment";
import logger from "../../../config/logger";
import twilio from "twilio";
interface ProviderConfig {
provider: {
identifier: string;
endpoint: {
variables?: {
env?: string[];
};
};
};
template: Record<string, any>;
providerToUse: string;
}
export const createTwilioProvider = (
providerConfig: ProviderConfig
): SMSProvider => {
// Validate environment variables
const requiredEnvVars = providerConfig.provider.endpoint.variables?.env || [];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
throw new Error(`${envVar} is required for Twilio provider`);
}
}
const client = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
const getSMSMessage = (
otp: string,
purpose: "LOGIN" | "SIGNUP" | "PASSWORD_RESET"
): string => {
const expiryMinutes = config.OTP_EXPIRY_MINUTES;
switch (purpose) {
case "LOGIN":
return `Your verification code is ${otp}. Valid for ${expiryMinutes} minutes.`;
case "SIGNUP":
return `Welcome! Your verification code is ${otp}. Valid for ${expiryMinutes} minutes.`;
case "PASSWORD_RESET":
return `Your password reset code is ${otp}. Valid for ${expiryMinutes} minutes.`;
}
};
const sendSMS = async (to: string, message: string): Promise<void> => {
try {
const result = await client.messages.create({
body: message,
from: process.env.TWILIO_FROM_NUMBER,
to: to,
});
logger.info(`SMS sent successfully via Twilio`, {
to,
messageId: result.sid,
status: result.status,
});
} catch (error) {
logger.error(`Failed to send SMS via Twilio`, { error, to });
throw new Error("Failed to send SMS");
}
};
return {
send: async (
to: string,
otp: string,
purpose: "LOGIN" | "SIGNUP" | "PASSWORD_RESET"
): Promise<void> => {
const message = getSMSMessage(otp, purpose);
await sendSMS(to, message);
},
sendSMS,
};
};Step 2: Register Provider
Update server/src/lib/providers/sms/index.ts:
import { SMSProvider } from "../interface";
import { createBulkSMSBDProvider } from "./bulksmsbd";
import { createTwilioProvider } from "./twilio"; // Add this
import logger from "../../../config/logger";
import { gqlClient } from "../../graphqlClient";
import { GET_SETTING } from "../../../controllers/otp/gql";
export const getSMSProvider = async (): Promise<SMSProvider> => {
const result = await gqlClient.query(GET_SETTING, {
where: {
identifier: { _eq: "OTP_PROVIDER" },
},
});
if (!result.settings_config || result.settings_config.length === 0) {
throw new Error("SMS provider configuration not found in settings");
}
const providerConfig = result.settings_config[0].value;
const provider = providerConfig.providerToUse;
logger.info(`Initializing SMS provider: ${provider}`);
if (provider === "bulksmsbd") {
return createBulkSMSBDProvider(providerConfig);
} else if (provider === "twilio") {
return createTwilioProvider(providerConfig); // Add this
} else {
throw new Error(`SMS provider "${provider}" is not supported`);
}
};Step 3: Add Database Configuration
Update the database with your new provider configuration:
UPDATE settings.config
SET value = '{
"provider": {
"identifier": "twilio",
"website": "https://www.twilio.com",
"endpoint": {
"variables": {
"env": ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN", "TWILIO_FROM_NUMBER"]
}
}
},
"template": {
"otp": {
"message": "Your OTP is {{otp}}",
"variables": ["otp"]
}
},
"providerToUse": "twilio"
}'::jsonb
WHERE identifier = 'OTP_PROVIDER';Step 4: Set Environment Variables
Add to server/.env:
TWILIO_ACCOUNT_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_FROM_NUMBER=+1234567890Development & Testing
Console Provider (Development)
For development and testing without sending actual SMS/emails, you can:
- Check the server logs to see generated OTP codes
- Use the logged OTP codes for verification
The OTP codes are logged as:
OTP sent successfully via PHONE
OTP Code (development): 123456Testing Provider Integration
Create a test script:
# Send OTP
curl -X POST http://localhost:9201/api/v1/otp/send \
-H "Content-Type: application/json" \
-d '{
"input": {
"identifier": "+8801712345678",
"identifierType": "PHONE",
"purpose": "LOGIN"
}
}'
# Verify OTP
curl -X POST http://localhost:9201/api/v1/otp/verify \
-H "Content-Type: application/json" \
-d '{
"input": {
"identifier": "+8801712345678",
"otp": "123456",
"purpose": "LOGIN"
}
}'Provider Interface
All providers must implement the following interface:
export interface OTPProvider {
send(
to: string,
otp: string,
purpose: "LOGIN" | "SIGNUP" | "PASSWORD_RESET"
): Promise<void>;
}
export interface SMSProvider extends OTPProvider {
sendSMS(to: string, message: string): Promise<void>;
}
export interface EmailProvider extends OTPProvider {
sendEmail(to: string, subject: string, body: string): Promise<void>;
}Troubleshooting
SMS Not Sending
Check environment variables:
bashcd server grep BULK_SMS .envVerify database configuration:
sqlSELECT * FROM settings.config WHERE identifier = 'OTP_PROVIDER';Check server logs:
bashtail -f server/logs/combined-*.logCommon errors:
SMS provider configuration not found: Database migration not appliedBULK_SMS_API_KEY is required: Environment variable missingBulkSMSBD rejected SMS: Invalid credentials or insufficient credits
Provider Not Loading
- Restart the server after changing environment variables
- Clear any cached connections
- Check that the provider name in database matches the code
Rate Limiting Issues
If you're hitting rate limits during testing:
Temporarily increase limits in
.env:envOTP_SEND_LIMIT_PER_HOUR=10 OTP_SEND_LIMIT_PER_IP_PER_HOUR=50Clear rate limit records:
sqlDELETE FROM user.otp_rate_limit WHERE created_at < NOW() - INTERVAL '1 hour';
Security Best Practices
- Never commit credentials: Always use environment variables
- Use HTTPS: Ensure your API endpoint uses HTTPS in production
- Monitor usage: Track SMS sending costs and set up alerts
- Rotate credentials: Regularly update API keys
- Test in staging: Verify provider configuration before production
- Rate limiting: Keep OTP rate limits enabled to prevent abuse
- Audit logs: Monitor OTP sending patterns for suspicious activity
Next Steps
Contributing
To add support for new providers:
- Implement the provider in
server/src/lib/providers/sms/oremail/ - Register it in the appropriate index file
- Document the required environment variables
- Add database migration for configuration
- Test thoroughly before submitting PR