# BulkSMSRates Node.js SDK

Official Node.js (ESM) client for the [BulkSMSRates](https://bulksmsrates.com) REST API.

## Requirements

- Node.js **18+** (uses built-in `fetch` and `AbortSignal.timeout`)
- No external dependencies

## Installation

```bash
# Download bulksmsrates.js and import directly (npm package coming soon)
cp bulksmsrates.js ./lib/
```

## Quick Start

```js
import { BulkSMSRates } from './bulksmsrates.js';

const client = new BulkSMSRates({
  apiKey: process.env.BULKSMS_API_KEY,
  apiSecret: process.env.BULKSMS_API_SECRET,
});

// Send a single SMS
const result = await client.sendSms('+447700900123', 'Hello from BulkSMSRates!');
console.log(`Message ID: ${result.id}, Status: ${result.status}`);

// Check balance
const balance = await client.getBalance();
console.log(`Balance: £${(balance.balance / 100).toFixed(2)}`);
```

## Authentication

### API Key + Secret (recommended for server-to-server)

```js
const client = new BulkSMSRates({
  apiKey: 'bsms_live_abc123',
  apiSecret: 'sk_live_xyz789',
});
```

### JWT Bearer Token

```js
const client = new BulkSMSRates({ accessToken: 'eyJhbGci...' });

// Or login interactively:
const client = new BulkSMSRates({ apiKey: 'temp', apiSecret: 'temp' });
const { access_token, refresh_token } = await client.login('user@example.com', 'password');
// access_token is automatically set in the client
```

## Sandbox Mode

```js
const client = new BulkSMSRates({
  apiKey: 'bsms_test_...',
  apiSecret: 'sk_test_...',
  sandbox: true,
});

const result = await client.sendSms('+447700900123', 'Test');
console.log(result.id);       // Fake message ID
console.log(result.sandbox);  // true

await client.resetSandbox();  // Clear all sandbox messages
```

## Send SMS

```js
// Basic
const result = await client.sendSms('+447700900123', 'Your OTP is 123456');

// With all options
const result = await client.sendSms('+447700900123', 'Hi {{name}}, code: {{code}}', {
  senderId: 'MyApp',
  variables: { name: 'Alice', code: '1234' },
  clientRef: 'order-abc',
  scheduledAt: '2026-03-15T09:00:00Z',
  validityMinutes: 60,
});
```

## Bulk SMS

```js
const result = await client.sendBulk(
  ['+447700900001', '+447700900002', '+447700900003'],
  'Flash sale! 30% off today only.',
  { senderId: 'MyShop' }
);

console.log(`Batch ${result.batch_id}: ${result.accepted} accepted`);
```

## Campaigns

```js
// Create
const campaign = await client.createCampaign({
  name: 'Spring Sale 2026',
  message: 'Hi {{name}}, get 30% off this weekend!',
  senderId: 'MyShop',
  scheduledAt: '2026-03-15T08:00:00Z',
});

// Submit
await client.submitCampaign(campaign.id);

// Monitor
const { sent, total } = await client.getCampaign(campaign.id);
console.log(`Progress: ${sent}/${total}`);

// Control
await client.pauseCampaign(campaign.id);
await client.resumeCampaign(campaign.id);
await client.cancelCampaign(campaign.id);
```

## Contacts

```js
// Create
const contact = await client.createContact({
  phone: '+447700900123',
  name: 'Alice Smith',
  email: 'alice@example.com',
  customFields: { tier: 'VIP' },
});

// List
const { data, total } = await client.listContacts({ page: 1, perPage: 50 });

// Update
await client.updateContact(contact.id, { name: 'Alice Jones' });

// Delete
await client.deleteContact(contact.id);

// Groups
const group = await client.createGroup('VIP Customers');
await client.addGroupMembers(group.id, [contact.id]);

// Tags
const tag = await client.createTag('Newsletter', '#0066cc');
```

## Billing

```js
const balance = await client.getBalance();
console.log(`Available: £${(balance.available / 100).toFixed(2)} ${balance.currency}`);

const { data: txns } = await client.listTransactions({ page: 1, perPage: 20 });
for (const t of txns) {
  console.log(`${t.type}: £${(t.amount / 100).toFixed(2)} — ${t.created_at}`);
}
```

## Error Handling

```js
import {
  BulkSMSRates,
  BulkSMSRatesError,
  RateLimitError,
  AuthError,
  InsufficientBalanceError,
  ValidationError,
} from './bulksmsrates.js';

try {
  await client.sendSms('+447700900123', 'Hello!');
} catch (err) {
  if (err instanceof InsufficientBalanceError) {
    console.error('Not enough credit:', err.details);
  } else if (err instanceof RateLimitError) {
    console.warn(`Rate limited. Retry in ${err.retryAfter}s`);
    await new Promise(r => setTimeout(r, err.retryAfter * 1000));
  } else if (err instanceof AuthError) {
    console.error('Authentication failed:', err.message);
  } else if (err instanceof ValidationError) {
    console.error('Invalid parameters:', err.message);
  } else if (err instanceof BulkSMSRatesError) {
    console.error(`API error ${err.statusCode} [${err.errorCode}]:`, err.message);
  } else {
    throw err;
  }
}
```

## Rate Limit Headers

```js
const result = await client.sendSms('+447700900123', 'Hello!');

console.log(client.rateLimit.limit);     // e.g. 100
console.log(client.rateLimit.remaining); // e.g. 99
console.log(client.rateLimit.reset);     // Unix timestamp
```

## Paginate All Results

```js
// Async generator — no manual cursor management
for await (const msg of client.paginate('/sms', { status: 'delivered' })) {
  console.log(msg.id, msg.status);
}

// Or collect all into an array
const messages = [];
for await (const msg of client.paginate('/sms')) {
  messages.push(msg);
}
```

## Auto-Retry

```js
// Retry up to 5 times with 2s base backoff
const client = new BulkSMSRates({
  apiKey: '...',
  apiSecret: '...',
  maxRetries: 5,
  retryBackoff: 2_000,
});

// Disable retries
const client = new BulkSMSRates({ apiKey: '...', apiSecret: '...', maxRetries: 0 });
```

## API Reference

| Method | Description |
|--------|-------------|
| `sendSms(dest, msg, opts?)` | Send a single SMS |
| `sendBulk(recipients, msg, opts?)` | Send to multiple recipients |
| `getMessage(id)` | Get message status |
| `listMessages(opts?)` | List messages (paginated) |
| `getBalance()` | Get wallet balance |
| `listTransactions(opts?)` | Transaction history |
| `createCampaign(opts)` | Create campaign |
| `listCampaigns(opts?)` | List campaigns |
| `getCampaign(id)` | Campaign details + progress |
| `submitCampaign(id)` | Submit for sending |
| `pauseCampaign(id)` | Pause campaign |
| `resumeCampaign(id)` | Resume campaign |
| `cancelCampaign(id)` | Cancel campaign |
| `createContact(opts)` | Create contact |
| `listContacts(opts?)` | List contacts |
| `updateContact(id, updates)` | Update contact |
| `deleteContact(id)` | Delete contact |
| `createGroup(name, desc?)` | Create group |
| `addGroupMembers(groupId, ids)` | Add contacts to group |
| `createTag(name, color?)` | Create tag |
| `me()` | Get current user |
| `login(email, password)` | Login and store token |
| `paginate(path, params?, size?)` | Async generator over pages |
| `resetSandbox()` | Reset sandbox state |

## License

MIT

## Support

- 📧 support@bulksmsrates.com
- 📖 https://bulksmsrates.com/docs
