semaphoreco
v1.0.3
Published
TypeScript/JavaScript client for Semaphore.co - Philippines SMS Gateway API for sending SMS and OTP messages
Maintainers
Readme
semaphoreco
TypeScript/JavaScript client for the Semaphore.co SMS Gateway API (Philippines).
Note: This package was previously published as
semaphore-client-js. If you're migrating, simply update your package name - the API is unchanged.
Installation
npm install semaphorecoOr with yarn:
yarn add semaphorecoOr with pnpm:
pnpm add semaphorecoQuick Start
import { SemaphoreClient } from 'semaphoreco';
const client = new SemaphoreClient({ apiKey: 'your-api-key' });
// Send an SMS
const result = await client.messages.send({
number: '639171234567',
message: 'Hello from Semaphore!',
sendername: 'SEMAPHORE',
});
console.log('Message ID:', result.message_id);Configuration
Create a client instance with your API key:
import { SemaphoreClient } from 'semaphoreco';
const client = new SemaphoreClient({
apiKey: 'your-api-key', // Required (or set SEMAPHORE_API_KEY env var)
baseUrl: 'https://api.semaphore.co/api/v4', // Optional, this is the default
timeout: 30000, // Optional, request timeout in ms (default: 30000)
onRetry: (attempt, delayMs) => {
console.log(`Retry attempt ${attempt} after ${delayMs}ms`);
},
});Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | SEMAPHORE_API_KEY env var | Your Semaphore API key |
| baseUrl | string | https://api.semaphore.co/api/v4 | API base URL |
| timeout | number | 30000 | Request timeout in milliseconds |
| onRetry | function | undefined | Callback when rate-limited request is retried |
Environment Variable
You can set your API key via environment variable instead of passing it to the constructor:
export SEMAPHORE_API_KEY=your-api-key// API key will be read from SEMAPHORE_API_KEY
const client = new SemaphoreClient();API Reference
Messages (client.messages)
send(params)
Send an SMS to one or more recipients.
// Send to a single recipient
const response = await client.messages.send({
number: '639171234567',
message: 'Hello!',
sendername: 'SEMAPHORE',
});
console.log(response.message_id); // 12345
console.log(response.status); // 'Queued'Bulk send - send to multiple recipients (up to 1000):
const responses = await client.messages.send({
number: ['639171234567', '639181234567', '639191234567'],
message: 'Hello everyone!',
sendername: 'SEMAPHORE',
});
// Returns array of responses
for (const response of responses) {
console.log(`Sent to ${response.recipient}: ${response.message_id}`);
}Request Parameters:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| number | string \| string[] | Yes | Recipient number(s). Max 1000 for bulk |
| message | string | Yes | Message content |
| sendername | string | Yes | Approved sender name |
Response:
interface SendMessageResponse {
message_id: number;
user_id: number;
user: string;
account_id: number;
account: string;
recipient: string;
message: string;
sender_name: string;
network: string;
status: string;
type: string;
source: string;
created_at: string;
updated_at: string;
}list(params?)
List sent messages with optional filtering.
// List all messages
const messages = await client.messages.list();
// List with filters
const filtered = await client.messages.list({
page: 1,
limit: 50,
status: 'Sent',
startDate: '2024-01-01',
endDate: '2024-01-31',
});Filter Parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| page | number | Page number for pagination |
| limit | number | Number of results per page |
| startDate | string | Filter by start date (YYYY-MM-DD) |
| endDate | string | Filter by end date (YYYY-MM-DD) |
| network | string | Filter by network |
| status | string | Filter by status |
| sendername | string | Filter by sender name |
get(id)
Retrieve a specific message by ID.
const message = await client.messages.get(12345);
console.log(message.status); // 'Sent'
console.log(message.recipient); // '639171234567'
console.log(message.message); // 'Hello!'OTP (client.otp)
Send OTP messages via priority route with no rate limit. Each OTP message costs 2 credits per 160 characters.
send(params)
Send an OTP message. The API generates the OTP code and replaces the {otp} placeholder in your message.
const response = await client.otp.send({
number: '639171234567',
message: 'Your verification code is {otp}',
sendername: 'MyApp',
});
console.log('OTP code:', response.code); // '123456'
console.log('Message ID:', response.message_id);With custom code length:
const response = await client.otp.send({
number: '639171234567',
message: 'Your code: {otp}',
sendername: 'MyApp',
code_length: 4, // Generate 4-digit code instead of default 6
});Request Parameters:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| number | string | Yes | Recipient phone number |
| message | string | Yes | Message template with {otp} placeholder |
| sendername | string | Yes | Approved sender name |
| code_length | number | No | OTP code length (default: 6) |
Response:
interface SendOtpResponse {
message_id: number;
code: string; // The generated OTP code
user_id: number;
user: string;
account_id: number;
account: string;
recipient: string;
message: string;
sender_name: string;
network: string;
status: string;
type: string;
source: string;
created_at: string;
updated_at: string;
}Account (client.account)
info()
Get account information including credit balance.
const info = await client.account.info();
console.log('Account:', info.account_name);
console.log('Status:', info.status);
console.log('Balance:', info.credit_balance);Response:
interface AccountInfo {
account_id: number;
account_name: string;
status: string;
credit_balance: string;
}transactions(params?)
Get transaction history with optional filtering.
// Get all transactions
const transactions = await client.account.transactions();
// Get with pagination and date range
const filtered = await client.account.transactions({
page: 1,
limit: 50,
startDate: '2024-01-01',
endDate: '2024-01-31',
});
for (const tx of filtered) {
console.log(`${tx.type}: ${tx.amount} credits - ${tx.description}`);
}Filter Parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| page | number | Page number for pagination |
| limit | number | Number of results per page |
| startDate | string | Filter by start date (YYYY-MM-DD) |
| endDate | string | Filter by end date (YYYY-MM-DD) |
Response:
interface Transaction {
id: number;
account_id: number;
user_id: number;
type: string;
amount: string;
balance_before: string;
balance_after: string;
description: string;
created_at: string;
}senderNames()
Get list of approved sender names.
const senderNames = await client.account.senderNames();
for (const sender of senderNames) {
console.log(`${sender.name}: ${sender.status}`);
}Response:
interface SenderName {
name: string;
status: string;
created_at: string;
}Error Handling
The library throws typed errors for different failure scenarios:
import {
SemaphoreClient,
SemaphoreError,
AuthenticationError,
RateLimitError,
ValidationError,
NetworkError,
TimeoutError,
} from 'semaphoreco';
const client = new SemaphoreClient({ apiKey: 'your-api-key' });
try {
await client.messages.send({
number: '639171234567',
message: 'Hello!',
sendername: 'SEMAPHORE',
});
} catch (error) {
if (error instanceof RateLimitError) {
// Rate limited (429) - retry after delay
console.log('Rate limited, retry after:', error.retryAfter, 'seconds');
} else if (error instanceof AuthenticationError) {
// Invalid API key (401)
console.log('Invalid API key');
} else if (error instanceof ValidationError) {
// Invalid request parameters (400)
console.log('Validation error:', error.message);
console.log('Field:', error.field);
} else if (error instanceof TimeoutError) {
// Request timed out
console.log('Request timed out after:', error.timeoutMs, 'ms');
} else if (error instanceof NetworkError) {
// Network connection failed
console.log('Network error:', error.message);
console.log('Cause:', error.cause);
} else if (error instanceof SemaphoreError) {
// Other API errors (5xx, etc.)
console.log('API error:', error.message);
console.log('Status:', error.statusCode);
}
}Error Types
| Error | Status Code | Description |
|-------|-------------|-------------|
| AuthenticationError | 401 | Invalid or missing API key |
| RateLimitError | 429 | Rate limit exceeded (120 req/min for standard SMS) |
| ValidationError | 400 | Invalid request parameters |
| NetworkError | - | Connection failure, DNS error |
| TimeoutError | - | Request exceeded timeout |
| SemaphoreError | 5xx | Server errors and other failures |
Automatic Retry
The client automatically retries rate-limited requests (429 responses) with exponential backoff. You can track retries with the onRetry callback:
const client = new SemaphoreClient({
apiKey: 'your-api-key',
onRetry: (attempt, delayMs) => {
console.log(`Rate limited. Retry attempt ${attempt} in ${delayMs}ms`);
},
});SMS Credits
- Standard SMS: 1 credit per 160 ASCII characters
- OTP SMS: 2 credits per 160 ASCII characters (priority route)
- Long messages: Split into 153-character segments
- Unicode: Limited to 70 characters per segment
Requirements
- Node.js 18.0.0 or later (uses native
fetch) - Also works with Deno, Bun, and other modern JavaScript runtimes
License
MIT
