sms-adapters

This skill provides comprehensive guidance for using @rytass/sms-adapter-* packages to integrate Taiwan SMS service providers.

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-adapters" with this command: npx skills add rytass/utils/rytass-utils-sms-adapters

Taiwan SMS Adapters

This skill provides comprehensive guidance for using @rytass/sms-adapter-* packages to integrate Taiwan SMS service providers.

Overview

All adapters implement the SMSService interface from @rytass/sms , providing a unified API across different providers:

Package Provider Description

@rytass/sms-adapter-every8d

Every8D (每日簡訊 / 互動資通) Taiwan SMS gateway by Interactive Communications

Base Interface (@rytass/sms)

All adapters share these core concepts:

SMSService - Main interface for SMS operations:

interface SMSService< Request extends SMSRequest, SendResponse extends SMSSendResponse, MultiTarget extends MultiTargetRequest,

{ send(request: Request[]): Promise<SendResponse[]>; send(request: Request): Promise<SendResponse>; send(request: MultiTarget): Promise<SendResponse[]>; }

Request Types:

  • SMSRequest

  • Single SMS message to one recipient

  • MultiTargetRequest

  • Same message to multiple recipients

Response Status:

  • SUCCESS

  • Message sent successfully

  • FAILED

  • Message delivery failed

Taiwan Phone Number Helpers (from @rytass/sms ):

// 台灣手機號碼正規表達式 const TAIWAN_PHONE_NUMBER_RE = /^(0|+?886-?)9\d{2}-?\d{3}-?\d{3}$/;

// 正規化台灣手機號碼(移除非數字字元,將 886 前綴轉為 0) function normalizedTaiwanMobilePhoneNumber(mobile: string): string; // '0987-654-321' → '0987654321' // '+886987654321' → '0987654321' // '886987654321' → '0987654321'

Installation

Install base package

npm install @rytass/sms

Choose the adapter for your provider

npm install @rytass/sms-adapter-every8d

Quick Start

Every8D (每日簡訊 / 互動資通)

import { SMSServiceEvery8D } from '@rytass/sms-adapter-every8d';

// Initialize SMS service const smsService = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, baseUrl: 'https://api.e8d.tw', // Default API endpoint onlyTaiwanMobileNumber: true, // Restrict to Taiwan numbers only });

// Send single SMS const result = await smsService.send({ mobile: '0987654321', text: 'Hello! This is a test message.', });

console.log('Message ID:', result.messageId); console.log('Status:', result.status); console.log('Mobile:', result.mobile);

Common Usage Patterns

Single SMS Delivery

Send a message to a single recipient:

// Basic single SMS const result = await smsService.send({ mobile: '0987654321', text: 'Your verification code is 123456', });

// Check delivery status if (result.status === SMSRequestResult.SUCCESS) { console.log('SMS sent successfully!'); console.log('Message ID:', result.messageId); console.log('Recipient:', result.mobile); } else { console.error('SMS delivery failed'); console.error('Error Code:', result.errorCode); console.error('Error Message:', result.errorMessage); }

Batch Messaging (Same Message to Multiple Recipients)

Send the same message to multiple recipients efficiently:

// Batch send with same message const results = await smsService.send({ mobileList: [ '0987654321', '0912345678', '0923456789', '0934567890' ], text: 'Important notification: Your order has been shipped!', });

// Process results console.log(Total sent: ${results.length});

const successful = results.filter(r => r.status === SMSRequestResult.SUCCESS); const failed = results.filter(r => r.status === SMSRequestResult.FAILED);

console.log(Successful: ${successful.length}); console.log(Failed: ${failed.length});

// Log failed deliveries failed.forEach(result => { console.error(Failed to send to ${result.mobile}:, result.errorMessage); });

Multi-Target Messaging (Different Messages)

Send different messages to different recipients:

// Send different messages to each recipient const results = await smsService.send([ { mobile: '0987654321', text: 'Dear John, your appointment is confirmed for tomorrow at 10 AM.', }, { mobile: '0912345678', text: 'Hi Mary, your package will arrive today between 2-4 PM.', }, { mobile: '0923456789', text: 'Hello Mike, thank you for your purchase! Your receipt is attached.', }, ]);

// Check each result results.forEach((result, index) => { console.log(Message ${index + 1} to ${result.mobile}: ${result.status});

if (result.status === SMSRequestResult.SUCCESS) { console.log( Message ID: ${result.messageId}); } else { console.error( Error: ${result.errorMessage}); } });

Taiwan Mobile Number Handling

Automatic Number Normalization

The adapter automatically normalizes Taiwan mobile numbers:

// All these formats are normalized to 0987654321 const validFormats = [ '0987654321', // Standard format '0987-654-321', // With dashes '+886987654321', // International format '886987654321', // International without + '+886-987654321', // International with dash after country code ];

// All will be normalized and send successfully for (const number of validFormats) { const result = await smsService.send({ mobile: number, text: 'Test message', });

// Result.mobile will always be '0987654321' console.log('Normalized number:', result.mobile); }

Number Validation

Validate Taiwan mobile numbers with strict mode:

// Enable Taiwan-only validation const strictService = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, onlyTaiwanMobileNumber: true, // Enable strict validation });

try { // This will work - valid Taiwan mobile number await strictService.send({ mobile: '0987654321', // Taiwan number (09xxxxxxxx) text: 'Valid Taiwan number', });

// This will throw error - international number await strictService.send({ mobile: '+1234567890', // Non-Taiwan number text: 'Invalid for Taiwan-only mode', }); } catch (error) { console.error('Number validation error:', error.message); // Error: +1234567890 is not taiwan mobile phone (onlyTaiwanMobileNumber option is true) }

// Taiwan number format requirements: // - Must start with 09 (or +8869, 8869) // - Total 10 digits after country code // - Examples: 0912345678, 0923456789, 0987654321

International Numbers

Send to international numbers when validation is disabled:

// Allow international numbers const globalService = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, onlyTaiwanMobileNumber: false, // Default: allow all numbers });

// Send to various countries const results = await globalService.send([ { mobile: '0987654321', text: 'Taiwan message' }, // Taiwan { mobile: '+85298765432', text: 'Hong Kong message' }, // Hong Kong { mobile: '+60123456789', text: 'Malaysia message' }, // Malaysia { mobile: '+6591234567', text: 'Singapore message' }, // Singapore ]);

// Note: Check with Every8D for international coverage and rates

Integration Examples

E-commerce Order Notifications

import { SMSServiceEvery8D } from '@rytass/sms-adapter-every8d'; import { SMSRequestResult } from '@rytass/sms';

class OrderNotificationService { private smsService: SMSServiceEvery8D;

constructor() { this.smsService = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, onlyTaiwanMobileNumber: true, }); }

/**

  • Send order confirmation SMS
  • @param order - Order object with customer details
  • @returns SMS delivery result */ async sendOrderConfirmation(order: { id: string; customerPhone: string; total: number; deliveryDate: string; trackingUrl: string; }): Promise<boolean> { const message = 訂單已確認! 訂單編號:${order.id} 金額:NT$${order.total} 預計送達:${order.deliveryDate} 追蹤連結:${order.trackingUrl} .trim();
const result = await this.smsService.send({
  mobile: order.customerPhone,
  text: message,
});

return result.status === SMSRequestResult.SUCCESS;

}

/**

  • Send shipping notification
  • @param order - Order object with tracking number
  • @returns SMS delivery result */ async sendShippingNotification(order: { id: string; customerPhone: string; trackingNumber: string; }): Promise<boolean> { const message = 您的訂單 #${order.id} 已出貨!追蹤號碼:${order.trackingNumber};
const result = await this.smsService.send({
  mobile: order.customerPhone,
  text: message,
});

if (result.status === SMSRequestResult.FAILED) {
  console.error('Failed to send shipping notification:', {
    orderId: order.id,
    errorCode: result.errorCode,
    errorMessage: result.errorMessage,
  });
}

return result.status === SMSRequestResult.SUCCESS;

}

/**

  • Send delivery notification to multiple orders
  • @param orders - Array of orders ready for delivery
  • @returns Delivery statistics */ async sendBulkDeliveryNotifications(orders: Array<{ id: string; customerPhone: string; }>): Promise<{ total: number; successful: number; failed: number; }> { const results = await this.smsService.send( orders.map(order => ({ mobile: order.customerPhone, text: 您的訂單 #${order.id} 將於今日送達,請留意收件。, })) );
const successful = results.filter(r => r.status === SMSRequestResult.SUCCESS).length;
const failed = results.filter(r => r.status === SMSRequestResult.FAILED).length;

return {
  total: results.length,
  successful,
  failed,
};

} }

Authentication & Verification

import { SMSServiceEvery8D } from '@rytass/sms-adapter-every8d'; import { SMSRequestResult } from '@rytass/sms';

class AuthSMSService { private smsService: SMSServiceEvery8D;

constructor() { this.smsService = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, onlyTaiwanMobileNumber: true, }); }

/**

  • Generate and send verification code
  • @param phoneNumber - Recipient phone number
  • @returns Verification code (store in cache/database) */ async sendVerificationCode(phoneNumber: string): Promise<string> { // Generate 6-digit code const code = Math.random().toString().slice(-6);
const message = `您的驗證碼是 ${code},10 分鐘內有效。請勿將此驗證碼告知他人。`;
const result = await this.smsService.send({
  mobile: phoneNumber,
  text: message,
});

if (result.status === SMSRequestResult.SUCCESS) {
  // Store code in Redis/cache with 10-minute expiration
  await this.storeVerificationCode(phoneNumber, code, 600);
  console.log('Verification code sent:', result.messageId);
  return code;
} else {
  throw new Error(`SMS delivery failed: ${result.errorMessage}`);
}

}

/**

  • Send 2FA code for login
  • @param phoneNumber - User's phone number
  • @param serviceName - Name of the service for branding
  • @returns void */ async send2FACode( phoneNumber: string, serviceName: string ): Promise<void> { const code = Math.random().toString().slice(-6);
const message = `${serviceName} 登入驗證碼:${code},5 分鐘內有效。`;
const result = await this.smsService.send({
  mobile: phoneNumber,
  text: message,
});

if (result.status === SMSRequestResult.SUCCESS) {
  await this.store2FACode(phoneNumber, code, 300); // 5 minutes
} else {
  throw new Error(`Failed to send 2FA code: ${result.errorMessage}`);
}

}

/**

  • Send password reset link
  • @param phoneNumber - User's phone number
  • @param resetLink - Password reset URL
  • @returns SMS delivery result */ async sendPasswordResetLink( phoneNumber: string, resetLink: string ): Promise<boolean> { const message = 密碼重設連結:${resetLink}。此連結將在 30 分鐘後失效。;
const result = await this.smsService.send({
  mobile: phoneNumber,
  text: message,
});

return result.status === SMSRequestResult.SUCCESS;

}

private async storeVerificationCode( phone: string, code: string, expirationSeconds: number ): Promise<void> { // Implementation depends on your storage solution // Redis, database, or in-memory cache // Example with Redis: // await redis.setex(verification:${phone}, expirationSeconds, code); }

private async store2FACode( phone: string, code: string, expirationSeconds: number ): Promise<void> { // Store with shorter expiration for 2FA // await redis.setex(2fa:${phone}, expirationSeconds, code); } }

Marketing Campaign Service

import { SMSServiceEvery8D } from '@rytass/sms-adapter-every8d'; import { SMSRequestResult } from '@rytass/sms';

class MarketingCampaignService { private smsService: SMSServiceEvery8D;

constructor() { this.smsService = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, onlyTaiwanMobileNumber: true, }); }

/**

  • Send promotional campaign to customers
  • @param customers - Customer list with phone numbers
  • @param campaignMessage - Campaign message text
  • @returns Campaign statistics */ async sendPromotionalCampaign( customers: Array<{ phoneNumber: string; name: string }>, campaignMessage: string ): Promise<{ total: number; successful: number; failed: number; successRate: number; }> { // Split into batches to avoid rate limiting const batchSize = 100; const batches: Array<typeof customers> = [];
for (let i = 0; i &#x3C; customers.length; i += batchSize) {
  batches.push(customers.slice(i, i + batchSize));
}

const allResults = [];

for (const batch of batches) {
  const phoneNumbers = batch.map(customer => customer.phoneNumber);

  try {
    const batchResult = await this.smsService.send({
      mobileList: phoneNumbers,
      text: campaignMessage,
    });

    allResults.push(...batchResult);

    // Add delay between batches to respect rate limits
    await this.delay(1000); // 1 second delay
  } catch (error) {
    console.error('Batch failed:', error);

    // Add failed entries for this batch
    phoneNumbers.forEach(phone => {
      allResults.push({
        mobile: phone,
        status: SMSRequestResult.FAILED,
        errorMessage: 'Batch processing error',
      });
    });
  }
}

return this.analyzeCampaignResults(allResults);

}

/**

  • Send personalized messages to customers
  • @param customers - Customer list with personalized data
  • @returns Campaign statistics */ async sendPersonalizedCampaign( customers: Array<{ phoneNumber: string; name: string; customMessage: string; }> ): Promise<{ total: number; successful: number; failed: number; successRate: number; }> { const results = await this.smsService.send( customers.map(customer => ({ mobile: customer.phoneNumber, text: customer.customMessage, })) );
return this.analyzeCampaignResults(results);

}

/**

  • Delay helper for rate limiting
  • @param ms - Milliseconds to delay
  • @returns Promise that resolves after delay */ private delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); }

/**

  • Analyze campaign results
  • @param results - Array of SMS send results
  • @returns Campaign statistics */ private analyzeCampaignResults( results: Array<{ status: SMSRequestResult; mobile: string }> ): { total: number; successful: number; failed: number; successRate: number; } { const successful = results.filter( r => r.status === SMSRequestResult.SUCCESS ).length; const failed = results.filter( r => r.status === SMSRequestResult.FAILED ).length;
return {
  total: results.length,
  successful,
  failed,
  successRate: (successful / results.length) * 100,
};

} }

Appointment Reminders

import { SMSServiceEvery8D } from '@rytass/sms-adapter-every8d'; import { SMSRequestResult } from '@rytass/sms';

class AppointmentReminderService { private smsService: SMSServiceEvery8D;

constructor() { this.smsService = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, onlyTaiwanMobileNumber: true, }); }

/**

  • Send appointment reminder
  • @param appointment - Appointment details
  • @returns SMS delivery result */ async sendAppointmentReminder(appointment: { customerPhone: string; customerName: string; date: string; time: string; location: string; doctorName?: string; }): Promise<boolean> { const message = appointment.doctorName ? 親愛的 ${appointment.customerName},提醒您與 ${appointment.doctorName} 醫師的預約:${appointment.date} ${appointment.time},地點:${appointment.location}。請提前 10 分鐘報到。 : 親愛的 ${appointment.customerName},提醒您的預約:${appointment.date} ${appointment.time},地點:${appointment.location}。請提前 10 分鐘報到。;
const result = await this.smsService.send({
  mobile: appointment.customerPhone,
  text: message,
});

return result.status === SMSRequestResult.SUCCESS;

}

/**

  • Send batch appointment reminders
  • @param appointments - Array of appointments
  • @returns Statistics of sent reminders */ async sendBatchReminders( appointments: Array<{ customerPhone: string; date: string; time: string; }> ): Promise<{ total: number; sent: number; failed: number; }> { const results = await this.smsService.send( appointments.map(apt => ({ mobile: apt.customerPhone, text: 預約提醒:${apt.date} ${apt.time}。請準時出席,如需取消請提前通知。, })) );
const sent = results.filter(
  r => r.status === SMSRequestResult.SUCCESS
).length;
const failed = results.filter(
  r => r.status === SMSRequestResult.FAILED
).length;

return {
  total: results.length,
  sent,
  failed,
};

} }

NestJS Integration

Basic Setup

// sms.module.ts import { Module, Global } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { SMSServiceEvery8D } from '@rytass/sms-adapter-every8d';

export const SMS_SERVICE = Symbol('SMS_SERVICE');

@Global() @Module({ imports: [ConfigModule], providers: [ { provide: SMS_SERVICE, useFactory: (config: ConfigService) => { return new SMSServiceEvery8D({ username: config.get('EVERY8D_USERNAME')!, password: config.get('EVERY8D_PASSWORD')!, onlyTaiwanMobileNumber: config.get('EVERY8D_TAIWAN_ONLY') === 'true', }); }, inject: [ConfigService], }, ], exports: [SMS_SERVICE], }) export class SMSModule {}

Using in Services

// notification.service.ts import { Injectable, Inject, Logger } from '@nestjs/common'; import { SMSServiceEvery8D } from '@rytass/sms-adapter-every8d'; import { SMSRequestResult } from '@rytass/sms'; import { SMS_SERVICE } from './sms.module';

@Injectable() export class NotificationService { private readonly logger = new Logger(NotificationService.name);

constructor( @Inject(SMS_SERVICE) private readonly smsService: SMSServiceEvery8D, ) {}

/**

  • Send verification code via SMS

  • @param phoneNumber - Recipient phone number

  • @param code - Verification code

  • @returns Whether SMS was sent successfully */ async sendVerificationCode( phoneNumber: string, code: string, ): Promise<boolean> { try { const result = await this.smsService.send({ mobile: phoneNumber, text: 您的驗證碼是 ${code},10 分鐘內有效。, });

    if (result.status === SMSRequestResult.SUCCESS) { this.logger.log(Verification code sent to ${phoneNumber}); return true; } else { this.logger.error( Failed to send verification code to ${phoneNumber}: ${result.errorMessage}, ); return false; } } catch (error) { this.logger.error('SMS sending error:', error); return false; } }

/**

  • Send bulk notification

  • @param phoneNumbers - Array of recipient phone numbers

  • @param message - Message to send

  • @returns Statistics of sent messages */ async sendBulkNotification( phoneNumbers: string[], message: string, ): Promise<{ total: number; successful: number; failed: number; }> { try { const results = await this.smsService.send({ mobileList: phoneNumbers, text: message, });

    const successful = results.filter( r => r.status === SMSRequestResult.SUCCESS, ).length; const failed = results.filter( r => r.status === SMSRequestResult.FAILED, ).length;

    this.logger.log( Bulk notification sent: ${successful} successful, ${failed} failed, );

    return { total: results.length, successful, failed, }; } catch (error) { this.logger.error('Bulk SMS sending error:', error); throw error; } } }

Usage in Controllers

// notification.controller.ts import { Controller, Post, Body } from '@nestjs/common'; import { NotificationService } from './notification.service';

@Controller('notifications') export class NotificationController { constructor(private readonly notificationService: NotificationService) {}

@Post('send-verification') async sendVerification( @Body() body: { phoneNumber: string; code: string }, ): Promise<{ success: boolean }> { const success = await this.notificationService.sendVerificationCode( body.phoneNumber, body.code, );

return { success };

}

@Post('send-bulk') async sendBulk( @Body() body: { phoneNumbers: string[]; message: string }, ): Promise<{ total: number; successful: number; failed: number; }> { return await this.notificationService.sendBulkNotification( body.phoneNumbers, body.message, ); } }

Configuration

Environment Variables

.env

EVERY8D_USERNAME=your_every8d_username EVERY8D_PASSWORD=your_every8d_password EVERY8D_BASE_URL=https://api.e8d.tw EVERY8D_TAIWAN_ONLY=true

TypeScript Configuration

// config.ts export interface SMSConfig { username: string; password: string; baseUrl?: string; onlyTaiwanMobileNumber?: boolean; }

export const smsConfig: SMSConfig = { username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, baseUrl: process.env.EVERY8D_BASE_URL || 'https://api.e8d.tw', onlyTaiwanMobileNumber: process.env.EVERY8D_TAIWAN_ONLY === 'true', };

Error Handling

Handling Delivery Failures

import { SMSServiceEvery8D } from '@rytass/sms-adapter-every8d'; import { SMSRequestResult } from '@rytass/sms'; import { Every8DError } from '@rytass/sms-adapter-every8d';

async function sendWithRetry( smsService: SMSServiceEvery8D, mobile: string, text: string, maxRetries: number = 3 ): Promise<boolean> { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const result = await smsService.send({ mobile, text });

  if (result.status === SMSRequestResult.SUCCESS) {
    console.log(`SMS sent successfully on attempt ${attempt}`);
    return true;
  }

  // Check error code
  if (result.errorCode === Every8DError.FORMAT_ERROR) {
    console.error('Invalid format - no retry needed');
    return false;
  }

  console.warn(`Attempt ${attempt} failed:`, result.errorMessage);

  if (attempt &#x3C; maxRetries) {
    // Wait before retry (exponential backoff)
    await new Promise(resolve =>
      setTimeout(resolve, Math.pow(2, attempt) * 1000)
    );
  }
} catch (error) {
  console.error(`Attempt ${attempt} error:`, error);

  if (attempt === maxRetries) {
    throw error;
  }
}

}

return false; }

Batch Error Handling

async function sendBatchWithErrorHandling( smsService: SMSServiceEvery8D, phoneNumbers: string[], message: string ): Promise<{ successful: string[]; failed: Array<{ phone: string; error: string }>; }> { const results = await smsService.send({ mobileList: phoneNumbers, text: message, });

const successful: string[] = []; const failed: Array<{ phone: string; error: string }> = [];

results.forEach(result => { if (result.status === SMSRequestResult.SUCCESS) { successful.push(result.mobile); } else { failed.push({ phone: result.mobile, error: result.errorMessage || 'Unknown error', }); } });

// Log failures if (failed.length > 0) { console.error('Failed deliveries:', failed); }

return { successful, failed }; }

Best Practices

Message Content

class MessageTemplates { /**

  • Verification code template
    • Keep message under 70 characters for single SMS
    • Include expiration time
    • Add security warning */ static verification(code: string): string { return 您的驗證碼是 ${code},10 分鐘內有效。請勿將此驗證碼告知他人。; }

/**

  • Order confirmation template
    • Include order number for reference
    • Keep essential info only */ static orderConfirmation(orderNumber: string, amount: number): string { return 訂單 ${orderNumber} 已確認。金額:NT$${amount}。感謝您的購買!; }

/**

  • Appointment reminder template
    • Include date, time, and location
    • Add action instructions */ static appointmentReminder(date: string, time: string): string { return 預約提醒:${date} ${time}。請提前 10 分鐘報到。如需取消請來電。; }

/**

  • Marketing campaign template
    • Include opt-out instructions for compliance
    • Keep under 70 characters if possible */ static promotion(discount: string): string { return 限時優惠!${discount} 折扣活動進行中。查看詳情:[連結]。回 N 取消訂閱。; } }

Security

// 1. Store credentials securely // ❌ Don't hardcode credentials const badService = new SMSServiceEvery8D({ username: 'myusername', password: 'mypassword', });

// ✅ Use environment variables const goodService = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, });

// 2. Validate phone numbers before sending function isValidTaiwanMobile(phone: string): boolean { const cleaned = phone.replace(/[^0-9]/g, '').replace(/^886/, '0'); return /^09\d{8}$/.test(cleaned); }

// 3. Implement rate limiting class RateLimitedSMSService { private lastSendTime = 0; private minInterval = 1000; // 1 second between sends

async send(smsService: SMSServiceEvery8D, data: any) { const now = Date.now(); const timeSinceLastSend = now - this.lastSendTime;

if (timeSinceLastSend &#x3C; this.minInterval) {
  await new Promise(resolve =>
    setTimeout(resolve, this.minInterval - timeSinceLastSend)
  );
}

const result = await smsService.send(data);
this.lastSendTime = Date.now();

return result;

} }

// 4. Log all SMS activities for audit async function sendWithAuditLog( smsService: SMSServiceEvery8D, mobile: string, text: string, userId?: string ): Promise<void> { const auditEntry = { timestamp: new Date(), userId, recipient: mobile, messageLength: text.length, action: 'SMS_SEND', };

try { const result = await smsService.send({ mobile, text });

await logAudit({
  ...auditEntry,
  status: result.status,
  messageId: result.messageId,
});

} catch (error) { await logAudit({ ...auditEntry, status: 'ERROR', error: error.message, }); throw error; } }

async function logAudit(entry: any): Promise<void> { // Save to database or logging service }

Performance

// 1. Batch messages efficiently // ❌ Don't send one by one async function inefficientSend(phones: string[], message: string) { for (const phone of phones) { await smsService.send({ mobile: phone, text: message }); } }

// ✅ Use batch send async function efficientSend(phones: string[], message: string) { await smsService.send({ mobileList: phones, text: message }); }

// 2. Handle large batches with chunking async function sendLargeBatch( smsService: SMSServiceEvery8D, phones: string[], message: string, chunkSize: number = 100 ): Promise<void> { for (let i = 0; i < phones.length; i += chunkSize) { const chunk = phones.slice(i, i + chunkSize);

await smsService.send({
  mobileList: chunk,
  text: message,
});

// Add delay between chunks
if (i + chunkSize &#x3C; phones.length) {
  await new Promise(resolve => setTimeout(resolve, 1000));
}

} }

Compliance

// 1. Respect user preferences and opt-out requests interface UserSMSPreference { phoneNumber: string; optedOut: boolean; marketingConsent: boolean; transactionalOnly: boolean; }

async function sendRespectingPreferences( smsService: SMSServiceEvery8D, user: UserSMSPreference, message: string, messageType: 'marketing' | 'transactional' ): Promise<boolean> { // Check if user opted out completely if (user.optedOut) { console.log('User opted out - skipping SMS'); return false; }

// Check marketing consent if (messageType === 'marketing' && !user.marketingConsent) { console.log('No marketing consent - skipping SMS'); return false; }

// Send the message const result = await smsService.send({ mobile: user.phoneNumber, text: message, });

return result.status === SMSRequestResult.SUCCESS; }

// 2. Include opt-out instructions in marketing messages function createMarketingMessage(content: string): string { return ${content}\n\n回覆 N 取消訂閱簡訊通知。; }

// 3. Maintain do-not-contact list class DoNotContactList { private blockedNumbers = new Set<string>();

async add(phoneNumber: string): Promise<void> { this.blockedNumbers.add(phoneNumber); // Persist to database }

async remove(phoneNumber: string): Promise<void> { this.blockedNumbers.delete(phoneNumber); // Update database }

isBlocked(phoneNumber: string): boolean { return this.blockedNumbers.has(phoneNumber); }

async filterAllowed(phoneNumbers: string[]): Promise<string[]> { return phoneNumbers.filter(phone => !this.isBlocked(phone)); } }

Internal Implementation Details

Empty Target Handling

傳入空陣列或空 mobileList 會拋出錯誤:

// 以下情況會拋出 'No target provided.' 錯誤 await smsService.send([]); // 空陣列 await smsService.send({ mobileList: [], text: 'Test' }); // 空 mobileList

Batch Message Optimization

相同訊息內容的多個請求會自動合併為單一 API 呼叫,提高發送效率:

// 這三個請求會合併成一個 API 呼叫 const results = await smsService.send([ { mobile: '0912345678', text: 'Hello' }, { mobile: '0923456789', text: 'Hello' }, // 相同訊息 { mobile: '0934567890', text: 'Hello' }, // 相同訊息 ]); // 內部會將 DEST 參數合併為 '0912345678,0923456789,0934567890'

Partial Delivery Failure Handling

當部分發送失敗時,API 回傳 unsend 數量,系統會從列表末端開始標記失敗:

// 假設發送 5 個號碼,API 回傳 unsend=2 // 則前 3 個標記為 SUCCESS,後 2 個標記為 FAILED const results = await smsService.send({ mobileList: ['0911...', '0922...', '0933...', '0944...', '0955...'], text: 'Test', }); // results[0..2].status === SMSRequestResult.SUCCESS // results[3..4].status === SMSRequestResult.FAILED

Troubleshooting

Common Issues

  1. Authentication Failure

Symptoms:

  • All messages fail to send

  • Error message about credentials

Solutions:

// Check credentials console.log('Username:', process.env.EVERY8D_USERNAME); console.log('Password length:', process.env.EVERY8D_PASSWORD?.length);

// Ensure no extra spaces in .env EVERY8D_USERNAME=your_username # ❌ Trailing space EVERY8D_USERNAME=your_username # ✅ No space

// Test with minimal code const testService = new SMSServiceEvery8D({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD', });

const result = await testService.send({ mobile: '0987654321', text: 'Test', });

console.log('Result:', result);

  1. Invalid Phone Number Format

Symptoms:

  • FORMAT_ERROR (-306) error code

  • Message fails for specific numbers

Solutions:

// Enable Taiwan-only validation const service = new SMSServiceEvery8D({ username: process.env.EVERY8D_USERNAME!, password: process.env.EVERY8D_PASSWORD!, onlyTaiwanMobileNumber: true, });

// Validate before sending function validateTaiwanMobile(phone: string): boolean { const normalized = phone.replace(/[^0-9]/g, '').replace(/^886/, '0'); return /^09\d{8}$/.test(normalized); }

if (!validateTaiwanMobile(phoneNumber)) { console.error('Invalid Taiwan mobile number:', phoneNumber); }

  1. Message Not Delivered

Symptoms:

  • Status shows SUCCESS but message not received

  • Delays in delivery

Solutions:

// 1. Check message content // - Avoid special characters that might cause issues // - Keep message under 70 characters for single SMS

// 2. Verify phone number is active // - Test with your own phone first

// 3. Check account balance // - Contact Every8D to verify account status

// 4. Log message ID for tracking const result = await smsService.send({ mobile, text }); console.log('Message ID for tracking:', result.messageId);

API Reference

SMSServiceEvery8D

Constructor Options:

Property Type Required Default Description

username

string

Yes

Every8D account username

password

string

Yes

Every8D account password

baseUrl

string

No 'https://api.e8d.tw'

API endpoint URL

onlyTaiwanMobileNumber

boolean

No false

Restrict to Taiwan mobile numbers only

Methods:

// Send single SMS send(request: Every8DSMSRequest): Promise<Every8DSMSSendResponse>

// Send multiple SMS with different messages send(requests: Every8DSMSRequest[]): Promise<Every8DSMSSendResponse[]>

// Send same message to multiple recipients send(request: Every8DSMSMultiTargetRequest): Promise<Every8DSMSSendResponse[]>

Request Types

interface Every8DSMSRequest { mobile: string; // Phone number (will be normalized) text: string; // Message content }

interface Every8DSMSMultiTargetRequest { mobileList: string[]; // Array of phone numbers text: string; // Message content (same for all) }

Response Types

interface Every8DSMSSendResponse { messageId?: string; // Message ID for tracking status: SMSRequestResult; // SUCCESS or FAILED mobile: string; // Normalized phone number errorMessage?: string; // Error message if failed errorCode?: Every8DError; // Error code if failed }

enum SMSRequestResult { SUCCESS = 'SUCCESS', FAILED = 'FAILED', }

enum Every8DError { FORMAT_ERROR = -306, UNKNOWN = -99, }

Advanced Topics

For creating new SMS adapters or extending functionality, see the SMS Development Guide.

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.

General

wms-module

No summary provided by upstream source.

Repository SourceNeeds Review
General

invoice-adapters

No summary provided by upstream source.

Repository SourceNeeds Review
General

payment-adapters

No summary provided by upstream source.

Repository SourceNeeds Review
General

quadrats-module

No summary provided by upstream source.

Repository SourceNeeds Review