SMS Business

SMS Provider Migration Checklist for Devs

Step-by-step checklist for migrating from one SMS or OTP provider to another. Covers API abstraction, testing, gradual rollout, monitoring, and rollback planning.

14 February 202612 min read

StartMessaging Team

Engineering

Switching SMS or OTP providers is something most development teams will face at least once. Maybe your current provider’s delivery rates have dropped. Maybe their pricing no longer makes sense at your current volume. Maybe you need better support, Indian-specific features, or simpler DLT compliance. Whatever the reason, migrating SMS providers requires careful planning to avoid disrupting your users.

This guide provides a complete, step-by-step checklist for migrating from one SMS or OTP provider to another. Follow it and you will get through the migration with zero downtime and minimal risk.

When to Migrate Providers

Migration carries risk and effort, so make sure the reasons justify it. Common valid reasons to switch providers include:

  • Declining delivery rates: If your OTP delivery success rate drops below 95%, users are experiencing failed logins and abandoned transactions. This directly impacts revenue.
  • Cost increases: Providers sometimes raise prices, especially after acquisitions or when your contract comes up for renewal. Compare your current effective rate against alternatives like StartMessaging at Rs 0.25 per OTP.
  • Poor support responsiveness: When delivery issues arise and your provider takes days to respond, every hour of degraded service costs you users and money.
  • DLT compliance burden: Managing your own DLT registration, template approvals, and compliance updates is time-consuming. Providers like StartMessaging handle this entirely.
  • Currency and billing complexity: If your provider bills in USD and you operate in INR, exchange rate fluctuations make cost planning difficult.
  • Missing features: You need idempotency keys, better status tracking, webhook notifications, or other features your current provider does not offer.

If you are experiencing two or more of these issues simultaneously, migration is likely overdue.

Pre-Migration Audit

Before writing any migration code, audit your current SMS usage. This step prevents surprises during the switch.

Checklist: Current Provider Inventory

  • List all SMS use cases: OTP login, OTP for transactions, order notifications, appointment reminders, marketing campaigns. Each may have different requirements for the new provider.
  • Document current API endpoints used: Which send, verify, status check, and webhook endpoints does your code call?
  • Record current sender IDs: What sender ID (header) appears on messages? Will this change with the new provider?
  • Measure current performance baselines: Average delivery time, delivery success rate, verification success rate, monthly volume by use case. You need these to compare against the new provider.
  • Identify all integration points: Search your codebase for the current provider’s domain, SDK imports, and API key references. Every integration point must be updated.
  • Check contractual obligations: Review your current contract for termination notice periods, minimum commitments, and any exit fees.
  • Inventory DLT assets: If you manage your own DLT registration, document all registered templates, entity IDs, and sender IDs. These may need to be recreated or transferred.

Building the Abstraction Layer

If your current code calls the SMS provider’s API directly throughout the codebase, the first step is to refactor into an abstraction layer. This makes the actual migration trivial and protects you from future provider changes.

// sms-provider.interface.ts
interface SmsProvider {
  sendOtp(phoneNumber: string, options?: SendOtpOptions): Promise<SendOtpResult>;
  verifyOtp(requestId: string, code: string): Promise<VerifyResult>;
  getDeliveryStatus(messageId: string): Promise<DeliveryStatus>;
}

interface SendOtpOptions {
  idempotencyKey?: string;
  expiryMinutes?: number;
}

interface SendOtpResult {
  requestId: string;
  expiresAt: string;
  provider: string; // Track which provider handled it
}

interface VerifyResult {
  verified: boolean;
  attemptsRemaining?: number;
}

type DeliveryStatus = 'pending' | 'delivered' | 'failed' | 'expired';

Implement this interface for your current provider first. Replace all direct API calls in your codebase with calls to this interface. Deploy and verify everything works exactly as before. Only then move to implementing the new provider.

// startmessaging-provider.ts
class StartMessagingProvider implements SmsProvider {
  private baseUrl = 'https://api.startmessaging.com';
  private apiKey: string;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  async sendOtp(phoneNumber: string, options?: SendOtpOptions): Promise<SendOtpResult> {
    const response = await fetch(`${this.baseUrl}/otp/send`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey,
      },
      body: JSON.stringify({
        phoneNumber,
        idempotencyKey: options?.idempotencyKey,
      }),
    });

    const { data } = await response.json();
    return {
      requestId: data.requestId,
      expiresAt: data.expiresAt,
      provider: 'startmessaging',
    };
  }

  async verifyOtp(requestId: string, code: string): Promise<VerifyResult> {
    const response = await fetch(`${this.baseUrl}/otp/verify`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey,
      },
      body: JSON.stringify({ requestId, otpCode: code }),
    });

    const { data } = await response.json();
    return { verified: data.verified };
  }

  async getDeliveryStatus(messageId: string): Promise<DeliveryStatus> {
    // Query message status from StartMessaging
    const response = await fetch(`${this.baseUrl}/messages/${messageId}`, {
      headers: { 'X-API-Key': this.apiKey },
    });
    const { data } = await response.json();
    return data.status;
  }
}

Setting Up the New Provider

With the abstraction layer in place, set up the new provider account and implementation:

  1. Create the account: Sign up at dashboard.startmessaging.com (or your chosen provider). Complete any verification steps.
  2. Generate API credentials: Create an API key for your production environment. Store it securely in your environment variables or secrets manager. Never commit it to source control.
  3. Add wallet credit: For prepaid providers like StartMessaging, add sufficient credit for testing and the initial rollout period. A starting balance of Rs 500 to Rs 1,000 is adequate for testing.
  4. Review the API documentation: Read the OTP API docs thoroughly. Note any differences in request format, response structure, error codes, or rate limits compared to your current provider.
  5. Implement the provider adapter: Write the implementation of your SmsProvider interface for the new provider. Map response formats to your standardized interface.
  6. Handle error mapping: Map the new provider’s error codes to your application’s error handling. Different providers use different HTTP status codes and error formats for the same conditions.

Testing Strategy

Never go directly from implementation to production. Follow this testing progression:

Unit Tests

Test the new provider adapter in isolation with mocked HTTP responses. Verify that request formatting, response parsing, and error handling all work correctly. Test edge cases: malformed phone numbers, expired OTPs, rate limiting responses, and network timeouts.

Integration Tests

Test against the actual new provider API in a staging environment. Send real OTPs to test phone numbers. Verify the complete send and verify cycle works end-to-end. Measure delivery latency.

Load Tests

If your production volume is significant (more than 10,000 OTPs per day), run a load test against the new provider. Send a burst of requests to verify rate limits, response times under load, and whether the provider throttles your account.

Canary Tests

Route a small percentage (1% to 5%) of production traffic to the new provider while the rest continues on the old provider. Monitor delivery rates and latency in real production conditions for at least 48 hours before expanding.

Gradual Rollout Plan

A gradual rollout minimizes risk by slowly shifting traffic to the new provider. Here is a proven rollout schedule:

PhaseTraffic SplitDurationSuccess Criteria
Canary5% new, 95% old2 daysDelivery rate within 1% of old provider
Early Rollout25% new, 75% old3 daysNo increase in support tickets
Mid Rollout50% new, 50% old3 daysLatency within acceptable range
Late Rollout90% new, 10% old3 daysAll metrics stable
Complete100% newOngoingOld provider decommissioned

Implement the traffic split using a feature flag or a simple configuration value. A random number check works for percentage-based routing:

// provider-router.ts
class ProviderRouter implements SmsProvider {
  constructor(
    private oldProvider: SmsProvider,
    private newProvider: SmsProvider,
    private newProviderPercentage: number, // 0 to 100
  ) {}

  async sendOtp(phoneNumber: string, options?: SendOtpOptions) {
    const useNew = Math.random() * 100 < this.newProviderPercentage;
    const provider = useNew ? this.newProvider : this.oldProvider;

    try {
      return await provider.sendOtp(phoneNumber, options);
    } catch (error) {
      // Fallback to old provider if new one fails during rollout
      if (useNew) {
        console.warn('New provider failed, falling back to old provider');
        return await this.oldProvider.sendOtp(phoneNumber, options);
      }
      throw error;
    }
  }

  // Similar pattern for verifyOtp...
}

The key detail: during the rollout phase, the new provider falls back to the old provider on failure. This means the worst case during migration is slightly slower delivery for some requests, never a complete failure.

Monitoring During Migration

Active monitoring during the migration is non-negotiable. Track these metrics continuously:

  • Delivery success rate: The percentage of OTPs successfully delivered. Compare new vs. old provider in real-time. Any drop below 95% should trigger investigation.
  • Average delivery latency: Time from API call to SMS delivery. Measure at the p50, p95, and p99 percentiles. Latency spikes at p99 can indicate provider-side queuing issues.
  • Verification success rate: The percentage of sent OTPs that are successfully verified. A drop here could indicate delivery issues (users not receiving the SMS) or code generation problems.
  • Error rates by type: Track 4xx and 5xx errors separately. 4xx errors usually indicate your integration issues. 5xx errors indicate provider-side problems.
  • Fallback trigger rate: How often the new provider fails and falls back to the old provider. If this exceeds 5%, pause the rollout and investigate.
  • Support ticket volume: Monitor for increases in user complaints about OTPs not being received or login issues.

Set up automated alerts for each metric crossing its threshold. Do not rely on manually checking dashboards during the migration period.

Rollback Plan

Every migration needs a tested rollback plan. Here is yours:

  1. Keep the old provider active: Do not cancel your old provider account or revoke API keys until at least two weeks after the migration is complete at 100%.
  2. One-line rollback: Your ProviderRouter should allow you to set newProviderPercentage back to 0 instantly. This routes all traffic back to the old provider with no code deployment needed. Use a feature flag service or environment variable.
  3. Verify rollback works: Before starting the migration, test the rollback by setting the percentage to 0 and confirming the old provider handles all traffic correctly.
  4. Handle in-flight OTPs: During rollback, OTPs sent via the new provider but not yet verified still need to verify against the new provider. Your verification logic should route to the correct provider based on which one issued the requestId.
  5. Communication plan: If a rollback is needed, notify your team. If users were affected, prepare a status page update.

Post-Migration Cleanup

After running at 100% on the new provider for at least two weeks with stable metrics, complete the cleanup:

  • Remove the old provider implementation: Delete the old provider adapter code, SDK dependencies, and configuration values. Keep the abstraction interface for future flexibility.
  • Remove the routing layer: Replace the ProviderRouter with a direct reference to the new provider. Simplify the code path now that migration is complete.
  • Cancel the old provider account: After confirming there are no remaining dependencies, terminate the old provider contract and revoke API keys.
  • Update documentation: Update internal documentation, runbooks, and environment variable templates to reflect the new provider.
  • Archive migration metrics: Save the comparative delivery metrics from the migration period. This data is valuable for future provider evaluations.
  • Conduct a retrospective: Document what went well and what could be improved. This saves time on any future provider migrations.

FAQ

Planning a migration to StartMessaging? Start by reviewing our OTP API documentation to understand the integration points, then check our pricing page to compare costs with your current provider. For a broader view of your options, see our guide to the best OTP APIs in India.

Ready to Send OTPs?

Integrate StartMessaging in 5 minutes. No DLT registration required.