Node.JS

Send SMS, MMS, and Email with the CCAI Node.js SDK. Manage A2P 10DLC compliance, webhooks, and campaigns programmatically.

Learn how to send your first SMS using the CCAI Node.js SDK

Prerequisites

  • Sign up for a CCAI Trial Account
  • Get your Client ID from Account → Settings
  • Create/Copy an API Key from Account Settings
  • Node.js 16+ installed

1. Install & Configuration

Install the SDK

npm install ccai-node

Environment Variables (Recommended)

Create a .env file in your project root:

CCAI_CLIENT_ID=your-client-id
CCAI_API_KEY=your-api-key

Install dotenv:

npm install dotenv

Initialize the Client

import { CCAI } from 'ccai-node';
import 'dotenv/config';

const ccai = new CCAI({
  clientId: process.env.CCAI_CLIENT_ID,
  apiKey: process.env.CCAI_API_KEY
});

Security Note: Never hardcode credentials in source files. Always use environment variables or a secrets manager.


2. Send SMS

Single SMS

ccai.sms.sendSingle(
  "Jane",
  "Smith",
  "+15559876543",
  "Hi ${firstName}, thanks for your interest!",
  "Single Message Test"
)
  .then(response => console.log('Success:', response))
  .catch(error => console.error('Error:', error));

Bulk SMS / Campaign

Send to multiple recipients in a single call:

const accounts = [
  { firstName: "John", lastName: "Doe", phone: "+15551234567" },
  { firstName: "Jane", lastName: "Smith", phone: "+15559876543" },
  { firstName: "Bob", lastName: "Johnson", phone: "+15551112222" }
];

ccai.sms.send(
  accounts,
  "Hello ${firstName} ${lastName}, this is a campaign message!",
  "Bulk SMS Campaign"
)
  .then(response => console.log('Campaign sent:', response))
  .catch(error => console.error('Error:', error));

SMS with Options

const options = {
  timeout: 60,
  retries: 3,
  onProgress: (status) => console.log(`${new Date().toISOString()} - ${status}`)
};

ccai.sms.send(accounts, message, "Campaign Title", options)
  .then(response => console.log('Success:', response))
  .catch(error => console.error('Error:', error));

3. Send MMS

Complete MMS Workflow (Single Step)

const account = {
  firstName: "John",
  lastName: "Doe",
  phone: "+15551234567"
};

const options = {
  timeout: 60,
  onProgress: (status) => console.log(`Progress: ${status}`)
};

ccai.mms.sendWithImage(
  "path/to/your/image.jpg",
  "image/jpeg",
  [account],
  "Hello ${firstName}, check out this image!",
  "MMS Campaign Example",
  options
)
  .then(response => console.log(`MMS sent! Campaign ID: ${response.campaignId}`))
  .catch(error => console.error('Error:', error));

Step-by-Step MMS Workflow

For more control over the upload process:

// Step 1: Get a signed URL for uploading
const uploadResponse = await ccai.mms.getSignedUploadUrl("image.jpg", "image/jpeg");
const { signedS3Url, fileKey } = uploadResponse;

// Step 2: Upload the image to the signed URL
const uploadSuccess = await ccai.mms.uploadImageToSignedUrl(
  signedS3Url,
  "path/to/your/image.jpg",
  "image/jpeg"
);

if (uploadSuccess) {
  // Step 3: Send the MMS with the uploaded image
  const response = await ccai.mms.send(
    fileKey,
    accounts,
    "Hello ${firstName}, check out this image!",
    "MMS Campaign Example"
  );
  console.log(`MMS sent! Campaign ID: ${response.campaignId}`);
}

Single MMS

const response = await ccai.mms.sendSingle(
  "your-client-id/campaign/image.jpg",
  "John",
  "Doe",
  "+15551234567",
  "Hello ${firstName}, check out this image!",
  "MMS Campaign"
);

Supported Media Types

Content TypeExtension
image/jpeg.jpg, .jpeg
image/png.png
image/gif.gif

4. Send Email

Single Email

const response = await ccai.email.sendSingle(
  "John",
  "Doe",
  "[email protected]",
  "Welcome to Our Service",
  "<p>Hello ${firstName},</p><p>Thank you for signing up!</p>",
  "[email protected]",
  "[email protected]",
  "Your Company",
  "Welcome Email"
);

console.log(`Email sent with ID: ${response.id}`);

Email Campaign (Multiple Recipients)

const emailAccounts = [
  { firstName: "John", lastName: "Doe", email: "[email protected]" },
  { firstName: "Jane", lastName: "Smith", email: "[email protected]" }
];

const campaign = {
  subject: "Monthly Newsletter",
  title: "July 2025 Newsletter",
  message: `
    <h1>Monthly Newsletter</h1>
    <p>Hello \${firstName},</p>
    <p>Here are our updates for this month...</p>
  `,
  senderEmail: "[email protected]",
  replyEmail: "[email protected]",
  senderName: "Your Company Newsletter",
  accounts: emailAccounts,
  campaignType: "EMAIL",
  addToList: "noList",
  contactInput: "accounts",
  fromType: "single",
  senders: []
};

const options = {
  onProgress: (status) => console.log(`Progress: ${status}`)
};

const campaignResponse = await ccai.email.sendCampaign(campaign, options);
console.log(`Email campaign sent with ID: ${campaignResponse.id}`);

Schedule an Email Campaign

const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(10, 0, 0, 0);

const scheduledCampaign = {
  subject: "Upcoming Event Reminder",
  title: "Event Reminder Campaign",
  message: `
    <h1>Reminder: Upcoming Event</h1>
    <p>Hello \${firstName},</p>
    <p>This is a reminder about our upcoming event tomorrow.</p>
  `,
  senderEmail: "[email protected]",
  replyEmail: "[email protected]",
  senderName: "Your Company Events",
  accounts: emailAccounts,
  scheduledTimestamp: tomorrow.toISOString(),
  scheduledTimezone: "America/New_York"
};

const scheduledResponse = await ccai.email.sendCampaign(scheduledCampaign);
console.log(`Email campaign scheduled with ID: ${scheduledResponse.id}`);

5. Short Links

Create trackable short links for your campaigns:

// Create a short link
const shortLink = await ccai.shortlink.create(
  "https://www.yourcompany.com/promo?utm_source=sms",
  "Summer Promo Link"
);

console.log(`Short URL: ${shortLink.url}`);
console.log(`Link ID: ${shortLink.id}`);

// Use in an SMS campaign
ccai.sms.send(
  accounts,
  `Hi \${firstName}, check out our summer deals: ${shortLink.url}`,
  "Summer Promo Campaign"
);

6. Voice

// Send a voice message
const response = await ccai.voice.send(
  accounts,
  "Hello, this is a reminder about your appointment tomorrow at 3 PM.",
  "Appointment Reminder"
);

console.log(`Voice campaign sent: ${response.campaignId}`);

7. Conversations & Inbox

Retrieve Conversation History

// Get conversation history for a phone number
const conversations = await ccai.inbox.getConversation("+15551234567");

conversations.forEach(msg => {
  console.log(`[${msg.direction}] ${msg.timestamp}: ${msg.message}`);
});

Manage Inbox State

// Get inbox messages
const inbox = await ccai.inbox.list();

inbox.forEach(thread => {
  console.log(`From: ${thread.from}, Last Message: ${thread.lastMessage}`);
});

8. A2P 10DLC Compliance

Brand Registration

Register your brand for A2P 10DLC compliance:

const brand = await ccai.compliance.registerBrand({
  legalName: "Your Company LLC",
  taxId: "12-3456789",
  taxIdCountry: "US",
  website: "https://www.yourcompany.com",
  vertical: "TECHNOLOGY",
  entityType: "PRIVATE_PROFIT",
  address: {
    street: "123 Main St",
    city: "San Francisco",
    state: "CA",
    postalCode: "94105",
    country: "US"
  },
  contactEmail: "[email protected]",
  contactPhone: "+14155551234"
});

console.log(`Brand ID: ${brand.brandId}`);
console.log(`Brand Status: ${brand.status}`);

Campaign Registration

After brand approval, register your messaging campaign:

const campaign = await ccai.compliance.registerCampaign({
  brandId: brand.brandId,
  useCase: "MARKETING",
  description: "Promotional messages for opted-in customers",
  messageFlow: "Customers opt-in via web form and receive promotional SMS",
  sampleMessages: [
    "Hi ${firstName}, check out our latest deals at https://example.com",
    "Your order #12345 has shipped! Track it here: https://example.com/track"
  ],
  helpMessage: "Reply HELP for assistance. Contact [email protected]",
  optOutMessage: "You have been unsubscribed. Reply START to re-subscribe."
});

console.log(`Campaign ID: ${campaign.campaignId}`);
console.log(`Campaign Status: ${campaign.status}`);

Check Registration Status

Poll for approval status:

// Check brand status
const brandStatus = await ccai.compliance.getBrandStatus(brand.brandId);
console.log(`Brand Status: ${brandStatus.status}`); // PENDING, APPROVED, REJECTED

// Check campaign status
const campaignStatus = await ccai.compliance.getCampaignStatus(campaign.campaignId);
console.log(`Campaign Status: ${campaignStatus.status}`); // PENDING, APPROVED, REJECTED

// Poll until approved
async function waitForApproval(brandId, intervalMs = 30000) {
  let status = 'PENDING';
  while (status === 'PENDING') {
    const result = await ccai.compliance.getBrandStatus(brandId);
    status = result.status;
    if (status === 'PENDING') {
      console.log('Still pending, checking again in 30s...');
      await new Promise(resolve => setTimeout(resolve, intervalMs));
    }
  }
  return status;
}

9. Webhook Management

Register a Webhook

const webhookConfig = {
  url: "https://your-webhook-endpoint.com/webhook",
  events: ["MESSAGE_SENT", "MESSAGE_RECEIVED"],
  secret: "your-webhook-secret"
};

const registration = await ccai.webhook.register(webhookConfig);
console.log(`Webhook registered with ID: ${registration.id}`);

List Webhooks

const webhooks = await ccai.webhook.list();
webhooks.forEach(wh => {
  console.log(`Webhook ID: ${wh.id}, URL: ${wh.url}`);
});

Update a Webhook

const updatedWebhook = await ccai.webhook.update(registration.id, {
  url: "https://your-updated-endpoint.com/webhook",
  events: ["MESSAGE_SENT"],
  secret: "your-updated-secret"
});

Delete a Webhook

const deleteResponse = await ccai.webhook.delete(registration.id);
console.log(`Webhook deleted: ${deleteResponse.success}`);

Webhook Event Types

EventDescription
DELIVERY_RECEIPTMessage delivery status update
INBOUND_MESSAGEIncoming message received
OPT_OUTContact opted out of messaging
MESSAGE_SENTOutbound message sent successfully
MESSAGE_RECEIVEDInbound message received

Webhook Event Payloads

Delivery Receipt:

{
  "message": "Hello John! We are testing the CCAI SMS functionality",
  "segments": 1,
  "smsSid": 141321,
  "messageStatus": "SENT",
  "totalPrice": 0.03,
  "to": "+15551234567"
}

Inbound Message:

{
  "campaign": {
    "id": 141293,
    "title": "Default Campaign",
    "message": "",
    "senderPhone": null,
    "createdAt": "2025-08-13T21:20:50.212623Z",
    "runAt": "null"
  },
  "from": "+15551234567",
  "to": "+14158735045",
  "message": "Reply text here"
}

Verify Webhook Signatures

Validate that incoming webhooks are genuinely from CCAI:

import express from 'express';
import crypto from 'crypto';

const app = express();
app.use(express.json());

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-ccai-signature'];
  const secret = process.env.WEBHOOK_SECRET;

  if (!verifyWebhookSignature(req.body, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the event
  const event = req.body;
  console.log('Received event:', event);

  if (event.messageStatus) {
    console.log(`Delivery receipt: ${event.messageStatus} for ${event.to}`);
  } else if (event.from && event.message) {
    console.log(`Inbound message from ${event.from}: ${event.message}`);
  }

  res.status(200).json({ received: true });
});

app.listen(3000, () => console.log('Webhook server running on port 3000'));

10. Testing Webhooks with Ngrok

Step 1: Install Ngrok

brew install ngrok

Step 2: Create a Webhook Server

// webhook-server.js
import express from 'express';
import 'dotenv/config';

const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  console.log('Received webhook event!');
  console.log('Headers:', JSON.stringify(req.headers, null, 2));
  console.log('Body:', JSON.stringify(req.body, null, 2));
  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Webhook server listening on http://localhost:3000');
});

Step 3: Start the Server

node webhook-server.js

Step 4: Start Ngrok

In a separate terminal:

ngrok http 3000

Copy the https://xxxxx.ngrok-free.app URL from the output.

Step 5: Configure CCAI

  1. Log in to your CCAI account
  2. Navigate to Settings → Integration
  3. Set your webhook URLs:
    • Inbound message callback: https://xxxxx.ngrok-free.app/webhook
    • Outbound delivery callback: https://xxxxx.ngrok-free.app/webhook

Step 6: Send a Test Message

import { CCAI } from 'ccai-node';
import 'dotenv/config';

const ccai = new CCAI({
  clientId: process.env.CCAI_CLIENT_ID,
  apiKey: process.env.CCAI_API_KEY
});

await ccai.sms.sendSingle(
  "John",
  "Doe",
  "+15551234567",
  "Hello ${firstName}! Testing webhooks.",
  "Webhook Test"
);

Your webhook server should receive the delivery notification.


11. Error Handling

Error Structure

try {
  const response = await ccai.sms.sendSingle(
    "John", "Doe", "+15551234567",
    "Test message", "Test"
  );
} catch (error) {
  console.error(`Error Code: ${error.code}`);
  console.error(`Message: ${error.message}`);
  console.error(`Status: ${error.status}`);
  console.error(`Details: ${JSON.stringify(error.details)}`);
}

Common Error Codes

CodeDescriptionResolution
40001Invalid API keyVerify your API key in Account Settings
40002Invalid request parametersCheck required fields and data formats
40003Rate limit exceededImplement exponential backoff
40004Insufficient creditsAdd credits to your account
40101Authentication failedVerify clientId and apiKey
40301Forbidden - insufficient permissionsCheck account permissions
40401Resource not foundVerify the resource ID exists
42201Invalid phone number formatUse E.164 format (+1XXXXXXXXXX)
42202Recipient opted outRemove contact from campaign
50001Internal server errorRetry with exponential backoff

Retry with Exponential Backoff

async function sendWithRetry(fn, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries || error.code === 40101) {
        throw error;
      }
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage
const response = await sendWithRetry(() =>
  ccai.sms.sendSingle("John", "Doe", "+15551234567", "Hello!", "Test")
);

12. Complete Module Reference

SMS Module (ccai.sms)

MethodDescription
send(accounts, message, title, options?)Send SMS to multiple recipients
sendSingle(firstName, lastName, phone, message, title, options?)Send SMS to one recipient

MMS Module (ccai.mms)

MethodDescription
sendWithImage(imagePath, contentType, accounts, message, title, options?)Upload and send MMS in one step
send(pictureFileKey, accounts, message, title, options?)Send MMS with pre-uploaded image
sendSingle(pictureFileKey, firstName, lastName, phone, message, title)Send MMS to one recipient
getSignedUploadUrl(fileName, fileType)Get a signed S3 URL for image upload
uploadImageToSignedUrl(signedUrl, filePath, contentType)Upload image to signed URL

Email Module (ccai.email)

MethodDescription
sendSingle(firstName, lastName, email, subject, message, senderEmail, replyEmail, senderName, title)Send email to one recipient
sendCampaign(campaign, options?)Send email campaign to multiple recipients

Voice Module (ccai.voice)

MethodDescription
send(accounts, message, title, options?)Send voice message to multiple recipients

Short Link Module (ccai.shortlink)

MethodDescription
create(url, title)Create a trackable short link

Inbox Module (ccai.inbox)

MethodDescription
list()List inbox threads
getConversation(phone)Get conversation history for a number

Webhook Module (ccai.webhook)

MethodDescription
register(config)Register a new webhook endpoint
list()List all registered webhooks
update(id, config)Update an existing webhook
delete(id)Delete a webhook
verifySignature(signature, payload, secret)Verify webhook signature
parseEvent(json)Parse a webhook event payload

Compliance Module (ccai.compliance)

MethodDescription
registerBrand(brandData)Register a brand for A2P 10DLC
registerCampaign(campaignData)Register a messaging campaign
getBrandStatus(brandId)Check brand registration status
getCampaignStatus(campaignId)Check campaign registration status

13. Options Object Reference

SMSOptions / MMSOptions

{
  timeout: 60,          // Request timeout in seconds
  retries: 3,           // Number of retry attempts
  onProgress: (status) => {}  // Progress callback function
}

EmailOptions

{
  onProgress: (status) => {}  // Progress callback function
}

License

This project is licensed under the MIT License - see the LICENSE file for details.

Resources