sms-development

SMS Development Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "sms-development" with this command: npx skills add rytass/utils/rytass-utils-sms-development

SMS Development Guide

This skill provides guidance for developers working with the @rytass/sms base package, including creating new SMS adapters for Taiwan SMS service providers.

Overview

The @rytass/sms package defines the core interfaces and types that all SMS adapters must implement. It follows the adapter pattern to provide a unified API across different SMS providers.

Architecture

@rytass/sms (Base Package) │ ├── SMSService<Request, SendResponse, MultiTarget> # Service interface ├── SMSRequest # Single message interface ├── SMSSendResponse # Response interface ├── MultiTargetRequest # Batch message interface ├── SMSRequestResult # Status enum └── Taiwan Phone Number Helpers # Normalization utilities

@rytass/sms-adapter-* # Provider implementations │ ├── [Provider]SMSService # Implements SMSService ├── [Provider]SMSRequest # Extends SMSRequest ├── [Provider]SMSSendResponse # Extends SMSSendResponse └── [Provider] specific types and errors

Installation

npm install @rytass/sms

Core Interfaces

SMSService

The main interface that all SMS adapters must implement:

/**

  • SMS service interface
  • @template Request - SMS request type extending SMSRequest
  • @template SendResponse - SMS response type extending SMSSendResponse
  • @template MultiTarget - Multi-target request type extending MultiTargetRequest */ interface SMSService< Request extends SMSRequest, SendResponse extends SMSSendResponse, MultiTarget extends MultiTargetRequest,

{ /**

  • Send multiple SMS messages with different content
  • @param request - Array of SMS requests
  • @returns Promise resolving to array of send responses */ send(request: Request[]): Promise<SendResponse[]>;

/**

  • Send single SMS message
  • @param request - Single SMS request
  • @returns Promise resolving to send response */ send(request: Request): Promise<SendResponse>;

/**

  • Send same message to multiple recipients
  • @param request - Multi-target request with recipient list
  • @returns Promise resolving to array of send responses */ send(request: MultiTarget): Promise<SendResponse[]>; }

Base Request Types

/**

  • Base SMS request interface / interface SMSRequest { /* Recipient mobile phone number */ mobile: string;

/** Message text content */ text: string; }

/**

  • Multi-target request for sending same message to multiple recipients / interface MultiTargetRequest { /* Array of recipient mobile phone numbers */ mobileList: string[];

/** Message text content (same for all recipients) */ text: string; }

Base Response Types

/**

  • SMS request result status / enum SMSRequestResult { /* Message sent successfully */ SUCCESS = 'SUCCESS',

/** Message delivery failed */ FAILED = 'FAILED', }

/**

  • Base SMS send response interface / interface SMSSendResponse { /* Unique message identifier (if provided by gateway) */ messageId?: string;

/** Delivery status */ status: SMSRequestResult;

/** Recipient mobile phone number (normalized) */ mobile: string; }

Taiwan Phone Number Utilities

The base package provides utilities for handling Taiwan mobile numbers:

/**

  • Regular expression for validating Taiwan mobile phone numbers
  • Matches formats:
    • 09XXXXXXXX (standard)
    • 0912-345-678 (with dashes)
    • 0912 345 678 (with spaces)
    • +8869XXXXXXXX (international)
    • 8869XXXXXXXX (international without +) */ const TAIWAN_PHONE_NUMBER_RE = /^(0|+?886-?)9\d{2}-?\d{3}-?\d{3}$/;

/**

  • Normalize Taiwan mobile phone number to standard format (09XXXXXXXX)
  • @param mobile - Phone number in any supported format
  • @returns Normalized phone number (09XXXXXXXX)
  • @example
  • normalizedTaiwanMobilePhoneNumber('0987-654-321') // '0987654321'
  • normalizedTaiwanMobilePhoneNumber('+886987654321') // '0987654321'
  • normalizedTaiwanMobilePhoneNumber('886987654321') // '0987654321' */ function normalizedTaiwanMobilePhoneNumber(mobile: string): string;

Implementation Guide

Step 1: Define Provider-Specific Types

Create interfaces extending the base types:

import { SMSRequest, SMSSendResponse, MultiTargetRequest, SMSRequestResult, } from '@rytass/sms';

/**

  • Provider-specific initialization options / export interface MyProviderSMSRequestInit { /* API username/account ID */ username: string;

/** API password/secret key */ password: string;

/** API base URL (optional, defaults to production) */ baseUrl?: string;

/** Restrict to Taiwan mobile numbers only */ onlyTaiwanMobileNumber?: boolean;

// Add any other provider-specific config }

/**

  • Provider-specific error codes / export enum MyProviderError { /* Invalid credentials */ INVALID_CREDENTIALS = -1,

/** Invalid phone number format */ FORMAT_ERROR = -2,

/** Insufficient account balance */ INSUFFICIENT_BALANCE = -3,

/** Rate limit exceeded */ RATE_LIMIT_EXCEEDED = -4,

/** Unknown error */ UNKNOWN = -99, }

/**

  • Provider-specific SMS request
  • Extends base SMSRequest with additional fields if needed */ export interface MyProviderSMSRequest extends SMSRequest { mobile: string; text: string;

// Add provider-specific fields if needed // priority?: 'low' | 'normal' | 'high'; // scheduledTime?: Date; }

/**

  • Provider-specific send response
  • Extends base SMSSendResponse with additional fields */ export interface MyProviderSMSSendResponse extends SMSSendResponse { messageId?: string; status: SMSRequestResult; mobile: string;

/** Error message if delivery failed */ errorMessage?: string;

/** Provider-specific error code */ errorCode?: MyProviderError;

// Add provider-specific fields if needed // cost?: number; // remainingBalance?: number; }

/**

  • Provider-specific multi-target request */ export interface MyProviderSMSMultiTargetRequest extends MultiTargetRequest { mobileList: string[]; text: string; }

Step 2: Implement the SMS Service

import { SMSService, SMSRequestResult, normalizedTaiwanMobilePhoneNumber, TAIWAN_PHONE_NUMBER_RE, } from '@rytass/sms'; import axios from 'axios';

/**

  • SMS service implementation for MyProvider
  • Implements the SMSService interface with provider-specific logic */ export class SMSServiceMyProvider implements SMSService< MyProviderSMSRequest, MyProviderSMSSendResponse, MyProviderSMSMultiTargetRequest

{ private readonly username: string; private readonly password: string; private readonly baseUrl: string; private readonly onlyTaiwanMobileNumber: boolean;

/**

  • Initialize SMS service
  • @param options - Provider configuration options */ constructor(options: MyProviderSMSRequestInit) { this.username = options.username; this.password = options.password; this.baseUrl = options.baseUrl || 'https://api.myprovider.com'; this.onlyTaiwanMobileNumber = options.onlyTaiwanMobileNumber || false; }

/**

  • Send SMS message(s)
  • Handles three sending patterns:
    1. Single SMS to one recipient
    1. Multiple SMS with different messages
    1. Same message to multiple recipients */ async send(requests: MyProviderSMSRequest[]): Promise<MyProviderSMSSendResponse[]>; async send(request: MyProviderSMSRequest): Promise<MyProviderSMSSendResponse>; async send(request: MyProviderSMSMultiTargetRequest): Promise<MyProviderSMSSendResponse[]>;

async send( requests: MyProviderSMSMultiTargetRequest | MyProviderSMSRequest | MyProviderSMSRequest[], ): Promise<MyProviderSMSSendResponse | MyProviderSMSSendResponse[]> { // Validate input if ( (Array.isArray(requests) && !requests.length) || ((requests as MyProviderSMSMultiTargetRequest).mobileList && !(requests as MyProviderSMSMultiTargetRequest).mobileList?.length) ) { throw new Error('No target provided.'); }

// Process and validate phone numbers
const processedRequests = this.processRequests(requests);

// Send to provider API
const results = await this.sendToProvider(processedRequests);

// Return results in appropriate format
return this.formatResults(requests, results);

}

/**

  • Process and validate phone numbers
  • @param requests - Raw requests
  • @returns Processed requests with normalized phone numbers */ private processRequests( requests: MyProviderSMSMultiTargetRequest | MyProviderSMSRequest | MyProviderSMSRequest[], ): Array<{ mobile: string; text: string }> { const requestArray = Array.isArray(requests) ? requests : [requests]; const processed: Array<{ mobile: string; text: string }> = [];
for (const request of requestArray) {
  if ((request as MyProviderSMSMultiTargetRequest).mobileList) {
    // Multi-target request
    const multiTarget = request as MyProviderSMSMultiTargetRequest;

    for (const mobile of multiTarget.mobileList) {
      const normalizedMobile = this.validateAndNormalizeMobile(mobile);
      processed.push({
        mobile: normalizedMobile,
        text: multiTarget.text,
      });
    }
  } else {
    // Single request
    const singleRequest = request as MyProviderSMSRequest;
    const normalizedMobile = this.validateAndNormalizeMobile(singleRequest.mobile);
    processed.push({
      mobile: normalizedMobile,
      text: singleRequest.text,
    });
  }
}

return processed;

}

/**

  • Validate and normalize phone number
  • @param mobile - Raw phone number
  • @returns Normalized phone number
  • @throws Error if number is invalid and onlyTaiwanMobileNumber is true */ private validateAndNormalizeMobile(mobile: string): string { // Check if Taiwan number if (TAIWAN_PHONE_NUMBER_RE.test(mobile)) { return normalizedTaiwanMobilePhoneNumber(mobile); }
// If strict Taiwan-only mode, reject non-Taiwan numbers
if (this.onlyTaiwanMobileNumber) {
  throw new Error(
    `${mobile} is not taiwan mobile phone (\`onlyTaiwanMobileNumber\` option is true)`
  );
}

// Return as-is for international numbers
return mobile;

}

/**

  • Send requests to provider API
  • @param requests - Processed requests
  • @returns API responses */ private async sendToProvider( requests: Array<{ mobile: string; text: string }>, ): Promise<Map<string, MyProviderSMSSendResponse>> { // Group requests by message text for batch optimization const batches = this.groupByMessage(requests); const results = new Map<string, MyProviderSMSSendResponse>();
// Send each batch
for (const [message, mobileList] of batches.entries()) {
  try {
    // Call provider API
    const response = await this.callProviderAPI(mobileList, message);

    // Process API response
    const batchResults = this.parseAPIResponse(response, mobileList, message);

    // Store results
    for (const [key, result] of batchResults.entries()) {
      results.set(key, result);
    }
  } catch (error) {
    // Handle API errors
    for (const mobile of mobileList) {
      results.set(`${message}:${mobile}`, {
        status: SMSRequestResult.FAILED,
        mobile,
        errorMessage: error.message,
        errorCode: MyProviderError.UNKNOWN,
      });
    }
  }
}

return results;

}

/**

  • Group requests by message text for batch sending
  • @param requests - Array of requests
  • @returns Map of message text to mobile numbers */ private groupByMessage( requests: Array<{ mobile: string; text: string }>, ): Map<string, string[]> { const batches = new Map<string, string[]>();
for (const request of requests) {
  const existing = batches.get(request.text) || [];
  batches.set(request.text, [...existing, request.mobile]);
}

return batches;

}

/**

  • Call provider API
  • IMPORTANT: Implement this method according to your provider's API specification
  • @param mobileList - Array of phone numbers
  • @param message - Message text
  • @returns API response
  • NOTE: Every8D uses the following API specification:
    • Endpoint: ${baseUrl}/API21/HTTP/SendSMS.ashx
    • Method: POST with application/x-www-form-urlencoded
    • Parameters: UID (username), PWD (password), MSG (message), DEST (comma-separated mobiles)
    • Response: CSV format: "credit,sent,cost,unsent,batchId" or "errorCode,errorMessage" */ private async callProviderAPI( mobileList: string[], message: string, ): Promise<any> { // Example implementation using Every8D API format // Other providers may use different endpoints and parameters const { data } = await axios.post( ${this.baseUrl}/API21/HTTP/SendSMS.ashx, new URLSearchParams({ UID: this.username, // Every8D uses UID for username PWD: this.password, // Every8D uses PWD for password MSG: message, // Every8D uses MSG for message content DEST: mobileList.join(','), // Every8D uses DEST for comma-separated recipients }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, } );
return data;

}

/**

  • Parse provider API response
  • IMPORTANT: Implement this method according to your provider's response format
  • @param response - API response data
  • @param mobileList - Array of phone numbers
  • @param message - Message text
  • @returns Map of results keyed by "message:mobile" */ private parseAPIResponse( response: any, mobileList: string[], message: string, ): Map<string, MyProviderSMSSendResponse> { const results = new Map<string, MyProviderSMSSendResponse>();
// Example parsing - REPLACE WITH ACTUAL PROVIDER RESPONSE PARSING
if (response.success) {
  // All succeeded
  for (const mobile of mobileList) {
    results.set(`${message}:${mobile}`, {
      messageId: response.messageId,
      status: SMSRequestResult.SUCCESS,
      mobile,
    });
  }
} else {
  // All failed
  for (const mobile of mobileList) {
    results.set(`${message}:${mobile}`, {
      status: SMSRequestResult.FAILED,
      mobile,
      errorMessage: response.errorMessage,
      errorCode: response.errorCode,
    });
  }
}

return results;

}

/**

  • Format results based on original request type

  • @param originalRequest - Original request

  • @param results - Processed results map

  • @returns Formatted response(s) */ private formatResults( originalRequest: MyProviderSMSMultiTargetRequest | MyProviderSMSRequest | MyProviderSMSRequest[], results: Map<string, MyProviderSMSSendResponse>, ): MyProviderSMSSendResponse | MyProviderSMSSendResponse[] { // Multi-target request if ((originalRequest as MyProviderSMSMultiTargetRequest).mobileList) { const multiTarget = originalRequest as MyProviderSMSMultiTargetRequest;

    return multiTarget.mobileList.map(mobile => { const normalizedMobile = TAIWAN_PHONE_NUMBER_RE.test(mobile) ? normalizedTaiwanMobilePhoneNumber(mobile) : mobile;

    return results.get(${multiTarget.text}:${normalizedMobile})!; }); }

// Array of requests
if (Array.isArray(originalRequest)) {
  return originalRequest.map(request => {
    const normalizedMobile = TAIWAN_PHONE_NUMBER_RE.test(request.mobile)
      ? normalizedTaiwanMobilePhoneNumber(request.mobile)
      : request.mobile;

    return results.get(`${request.text}:${normalizedMobile}`)!;
  });
}

// Single request
const singleRequest = originalRequest as MyProviderSMSRequest;
const normalizedMobile = TAIWAN_PHONE_NUMBER_RE.test(singleRequest.mobile)
  ? normalizedTaiwanMobilePhoneNumber(singleRequest.mobile)
  : singleRequest.mobile;

return results.get(`${singleRequest.text}:${normalizedMobile}`)!;

} }

Step 3: Export Public API

// index.ts export { SMSServiceMyProvider } from './sms-service-my-provider'; export * from './typings';

Step 4: Add Tests

// tests/sms-service-my-provider.spec.ts import { SMSServiceMyProvider } from '../src/sms-service-my-provider'; import { SMSRequestResult } from '@rytass/sms';

describe('SMSServiceMyProvider', () => { let smsService: SMSServiceMyProvider;

beforeEach(() => { smsService = new SMSServiceMyProvider({ username: 'test-username', password: 'test-password', onlyTaiwanMobileNumber: true, }); });

describe('send - single SMS', () => { it('should send single SMS successfully', async () => { const result = await smsService.send({ mobile: '0987654321', text: 'Test message', });

  expect(result.status).toBe(SMSRequestResult.SUCCESS);
  expect(result.mobile).toBe('0987654321');
  expect(result.messageId).toBeDefined();
});

it('should normalize Taiwan phone number', async () => {
  const result = await smsService.send({
    mobile: '+886987654321',
    text: 'Test message',
  });

  expect(result.mobile).toBe('0987654321');
});

it('should reject non-Taiwan number when onlyTaiwanMobileNumber is true', async () => {
  await expect(
    smsService.send({
      mobile: '+1234567890',
      text: 'Test message',
    })
  ).rejects.toThrow('is not taiwan mobile phone');
});

});

describe('send - batch SMS', () => { it('should send same message to multiple recipients', async () => { const results = await smsService.send({ mobileList: ['0987654321', '0912345678', '0923456789'], text: 'Batch message', });

  expect(results).toHaveLength(3);
  expect(results.every(r => r.status === SMSRequestResult.SUCCESS)).toBe(true);
});

it('should send different messages to multiple recipients', async () => {
  const results = await smsService.send([
    { mobile: '0987654321', text: 'Message 1' },
    { mobile: '0912345678', text: 'Message 2' },
    { mobile: '0923456789', text: 'Message 3' },
  ]);

  expect(results).toHaveLength(3);
  expect(results.every(r => r.status === SMSRequestResult.SUCCESS)).toBe(true);
});

});

describe('error handling', () => { it('should handle API errors gracefully', async () => { // Mock API error // ... test implementation });

it('should return FAILED status on delivery failure', async () => {
  // Mock failed delivery
  // ... test implementation
});

}); });

Implementation Checklist

When implementing a new SMS adapter, ensure:

Required Features

  • Interface Implementation: Implements SMSService<Request, SendResponse, MultiTarget>

  • Single SMS: Supports sending single SMS to one recipient

  • Batch SMS: Supports sending same message to multiple recipients

  • Multi-target: Supports sending different messages to multiple recipients

  • Phone Validation: Validates phone numbers before sending

  • Number Normalization: Uses normalizedTaiwanMobilePhoneNumber() for Taiwan numbers

  • Error Handling: Returns appropriate error codes and messages

  • Type Safety: All methods have proper TypeScript types

Recommended Features

  • Taiwan Number Support: Uses TAIWAN_PHONE_NUMBER_RE for validation

  • Strict Mode: Implements onlyTaiwanMobileNumber option

  • International Support: Handles international numbers when strict mode is disabled

  • Message Batching: Groups messages for efficient API calls

  • Rate Limiting: Implements rate limiting if required by provider

  • Retry Logic: Implements retry for transient failures

  • Logging: Logs API calls and errors for debugging

  • Documentation: Includes comprehensive JSDoc comments

Quality Assurance

  • Unit Tests: Comprehensive test coverage (>80%)

  • Integration Tests: Tests against provider API (staging environment)

  • Error Cases: Tests all error scenarios

  • Edge Cases: Tests edge cases (empty lists, invalid numbers, etc.)

  • Performance: Tests batch performance with large recipient lists

  • README: Complete README with examples and API reference

Best Practices

  1. Phone Number Handling

// ✅ GOOD: Use provided utilities import { normalizedTaiwanMobilePhoneNumber, TAIWAN_PHONE_NUMBER_RE } from '@rytass/sms';

private validateMobile(mobile: string): string { if (TAIWAN_PHONE_NUMBER_RE.test(mobile)) { return normalizedTaiwanMobilePhoneNumber(mobile); }

if (this.onlyTaiwanMobileNumber) { throw new Error(Invalid Taiwan mobile number: ${mobile}); }

return mobile; }

// ❌ BAD: Custom regex without normalization private validateMobile(mobile: string): string { if (!/^09\d{8}$/.test(mobile)) { throw new Error('Invalid number'); } return mobile; }

  1. Error Handling

// ✅ GOOD: Detailed error information catch (error) { return { status: SMSRequestResult.FAILED, mobile, errorMessage: error.response?.data?.message || error.message, errorCode: this.mapProviderErrorCode(error.response?.data?.code), }; }

// ❌ BAD: Generic error without details catch (error) { return { status: SMSRequestResult.FAILED, mobile, }; }

  1. Batch Optimization

// ✅ GOOD: Group by message for efficiency private groupByMessage(requests: Array<{ mobile: string; text: string }>) { const batches = new Map<string, string[]>();

for (const request of requests) { const existing = batches.get(request.text) || []; batches.set(request.text, [...existing, request.mobile]); }

return batches; }

// ❌ BAD: Send each message individually for (const request of requests) { await this.callAPI(request.mobile, request.text); }

  1. Type Safety

// ✅ GOOD: Strict typing with generics export class SMSServiceMyProvider implements SMSService< MyProviderSMSRequest, MyProviderSMSSendResponse, MyProviderSMSMultiTargetRequest

{ // ... }

// ❌ BAD: Using any or loose typing export class SMSServiceMyProvider { async send(request: any): Promise<any> { // ... } }

  1. Configuration

// ✅ GOOD: Environment-based configuration export class SMSServiceMyProvider { constructor(options: MyProviderSMSRequestInit) { this.baseUrl = options.baseUrl || this.getDefaultBaseUrl(); }

private getDefaultBaseUrl(): string { return process.env.NODE_ENV === 'production' ? 'https://api.myprovider.com' : 'https://api-staging.myprovider.com'; } }

// ❌ BAD: Hardcoded production URL export class SMSServiceMyProvider { private baseUrl = 'https://api.myprovider.com'; }

API Reference

Base Package Exports

// Types export { SMSService, // Service interface SMSRequest, // Single message interface SMSSendResponse, // Response interface MultiTargetRequest, // Batch message interface SMSRequestResult, // Status enum };

// Utilities export { TAIWAN_PHONE_NUMBER_RE, // Taiwan number regex normalizedTaiwanMobilePhoneNumber, // Normalization function };

Taiwan Number Validation

Supported Formats:

  • 09XXXXXXXX

  • Standard Taiwan format

  • 0912-345-678

  • With dashes

  • 0912 345 678

  • With spaces

  • +8869XXXXXXXX

  • International with +

  • 8869XXXXXXXX

  • International without +

Normalization Output:

  • Always returns 09XXXXXXXX format

  • Removes dashes, spaces, and country code

  • Converts +886 or 886 prefix to 0

Examples

Minimal Adapter Implementation

import { SMSService, SMSRequest, SMSSendResponse, MultiTargetRequest, SMSRequestResult, } from '@rytass/sms';

interface SimpleSMSRequest extends SMSRequest { mobile: string; text: string; }

interface SimpleSMSResponse extends SMSSendResponse { messageId?: string; status: SMSRequestResult; mobile: string; }

interface SimpleMultiTargetRequest extends MultiTargetRequest { mobileList: string[]; text: string; }

export class SimpleSMSService implements SMSService< SimpleSMSRequest, SimpleSMSResponse, SimpleMultiTargetRequest

{ async send(requests: SimpleSMSRequest[]): Promise<SimpleSMSResponse[]>; async send(request: SimpleSMSRequest): Promise<SimpleSMSResponse>; async send(request: SimpleMultiTargetRequest): Promise<SimpleSMSResponse[]>;

async send(request: any): Promise<any> { // Minimal implementation if (Array.isArray(request)) { return Promise.all(request.map(r => this.sendSingle(r))); }

if (request.mobileList) {
  return Promise.all(
    request.mobileList.map(mobile =>
      this.sendSingle({ mobile, text: request.text })
    )
  );
}

return this.sendSingle(request);

}

private async sendSingle(request: SimpleSMSRequest): Promise<SimpleSMSResponse> { // Call provider API // Return response return { messageId: 'MSG-' + Date.now(), status: SMSRequestResult.SUCCESS, mobile: request.mobile, }; } }

Common Provider Patterns

HTTP-based API

Most Taiwan SMS providers use HTTP POST:

private async callProviderAPI(mobile: string, text: string): Promise<any> { const { data } = await axios.post( ${this.baseUrl}/api/sms/send, new URLSearchParams({ username: this.username, password: this.password, mobile: mobile, message: text, }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, } );

return data; }

CSV Response Format

Some providers return CSV responses:

private parseCSVResponse(data: string): { messageId: string; status: SMSRequestResult; } { const [credit, sent, cost, unsent, messageId] = data.split(',');

return { messageId, status: messageId ? SMSRequestResult.SUCCESS : SMSRequestResult.FAILED, }; }

Signature-based Authentication

Some providers require request signatures:

import crypto from 'crypto';

private generateSignature(params: Record<string, string>): string { const sorted = Object.keys(params) .sort() .map(key => ${key}=${params[key]}) .join('&');

return crypto .createHash('md5') .update(sorted + this.secretKey) .digest('hex'); }

Troubleshooting

Common Issues

TypeScript Errors:

// Error: Type 'X' is not assignable to type 'SMSService<...>' // Solution: Ensure all three send() overloads are implemented

async send(requests: MyProviderSMSRequest[]): Promise<MyProviderSMSSendResponse[]>; async send(request: MyProviderSMSRequest): Promise<MyProviderSMSSendResponse>; async send(request: MyProviderSMSMultiTargetRequest): Promise<MyProviderSMSSendResponse[]>;

Phone Number Validation:

// Issue: Numbers not being normalized correctly // Solution: Use TAIWAN_PHONE_NUMBER_RE before normalizing

if (TAIWAN_PHONE_NUMBER_RE.test(mobile)) { mobile = normalizedTaiwanMobilePhoneNumber(mobile); }

Batch Optimization:

// Issue: Too many API calls // Solution: Group requests by message text

const batches = requests.reduce((map, req) => { const list = map.get(req.text) || []; map.set(req.text, [...list, req.mobile]); return map; }, new Map<string, string[]>());

Publishing Checklist

Before publishing your adapter:

  • Package name follows convention: @rytass/sms-adapter-{provider}

  • Peer dependency on @rytass/sms is declared

  • All exports are properly typed

  • README includes installation, usage, and examples

  • Tests pass with >80% coverage

  • TypeScript builds without errors

  • Package builds with npm run build

  • Version follows semantic versioning

  • CHANGELOG.md is updated

  • License is included (MIT recommended)

Resources

For reference implementations:

  • Every8D Adapter - Complete reference implementation (互動資通 / Interactive Communications)

  • Base Package - Core interfaces and utilities

For usage guidance:

  • SMS Adapters Skill - User guide for SMS adapters

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

logistics-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

storage-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

invoice-development

No summary provided by upstream source.

Repository SourceNeeds Review