Channels


Multi-Channel Messaging

Send SMS, Email, and WhatsApp messages through NotifyHub's unified API. This guide covers all three channels with detailed examples and best practices.


Overview

NotifyHub provides a single endpoint for sending messages across all channels:

POST https://api.notifyhub.com/notifications/send

Key Features:

  • Unified interface for all channels
  • Single or bulk sending
  • Automatic recipient validation
  • Delivery tracking and logging
  • Per-organization credential overrides

SMS via Lancola

Send text messages to mobile phones worldwide.

Basic SMS

Request:

{
  "type": "sms",
  "to": "+254712345678",
  "message": "Your OTP code is 483920. Valid for 10 minutes."
}

Response:

{
  "recipient": "+254712345678",
  "status": "success"
}

Phone Number Format

Requirements:

  • International format with country code
  • Can include + prefix (will be removed automatically)
  • No spaces, dashes, or parentheses

Valid Formats:

+254712345678  ✅
254712345678   ✅
+1-555-123-4567  ❌ (will be normalized to +15551234567)

Country Code Examples:

Kenya: +254
USA: +1
UK: +44
India: +91
Nigeria: +234

Bulk SMS

Send to multiple recipients:

Request:

{
  "type": "sms",
  "to": ["+254712345678", "+254723456789", "+254734567890"],
  "message": "Flash sale! 50% off all items today only. Shop now: https://example.com"
}

Response:

[
  { "recipient": "+254712345678", "status": "success" },
  { "recipient": "+254723456789", "status": "success" },
  { "recipient": "+254734567890", "status": "failed", "error": "Invalid number" }
]

Message Length

Standard SMS: 160 characters Long SMS: Automatically split into multiple parts

  • 1 part: 160 characters
  • 2 parts: 306 characters (153 × 2)
  • 3 parts: 459 characters (153 × 3)

Cost Calculation:

1-160 chars = 1 SMS unit
161-306 chars = 2 SMS units
307-459 chars = 3 SMS units

Best Practice: Keep messages under 160 characters to minimize costs.

Character Encoding

GSM-7 Alphabet (160 chars/SMS):

  • Standard ASCII letters (A-Z, a-z)
  • Numbers (0-9)
  • Common symbols (. , ! ? @ - _)

Unicode (70 chars/SMS):

  • Emojis 😊
  • Special characters (€, £, ¥)
  • Non-Latin scripts (Arabic, Chinese, etc.)

Example:

// GSM-7 (160 chars)
"Your code is 123456. Valid for 10 min."  // ✅ 40 chars
 
// Unicode (70 chars)
"Your code is 123456 😊"  // ⚠️ Uses Unicode due to emoji

SMS Use Cases

OTP/Verification Codes:

{
  "type": "sms",
  "to": "+254712345678",
  "message": "Your verification code is 483920. Valid for 10 minutes. Do not share this code."
}

Order Confirmations:

{
  "type": "sms",
  "to": "+254712345678",
  "message": "Order #12345 confirmed! Estimated delivery: Jan 30. Track: https://track.example.com/12345"
}

Appointment Reminders:

{
  "type": "sms",
  "to": "+254712345678",
  "message": "Reminder: Your appointment with Dr. Smith is tomorrow at 2:00 PM. Reply CONFIRM or CANCEL."
}

Promotional Messages:

{
  "type": "sms",
  "to": "+254712345678",
  "message": "Flash Sale! 50% off all items today only. Use code FLASH50. Shop now: https://example.com"
}

Complete SMS Example

async function sendSMS(to, message, token) {
  try {
    const response = await fetch('https://api.notifyhub.com/notifications/send', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        type: 'sms',
        to,
        message
      })
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    }
    
    const result = await response.json();
    console.log('SMS sent:', result);
    return result;
  } catch (error) {
    console.error('SMS error:', error);
    throw error;
  }
}
 
// Usage
await sendSMS('+254712345678', 'Hello from NotifyHub!', token);

Email via SMTP

Send professional emails with HTML formatting and attachments.

Basic Email

Request:

{
  "type": "email",
  "to": "user@example.com",
  "subject": "Welcome to NotifyHub",
  "message": "<h1>Welcome!</h1><p>Thank you for signing up.</p>"
}

Response:

{
  "recipient": "user@example.com",
  "status": "success"
}

HTML vs Plain Text

HTML Email (recommended for newsletters, marketing):

{
  "type": "email",
  "to": "user@example.com",
  "subject": "Monthly Newsletter",
  "message": "<html><body><h1>January Newsletter</h1><p>Check out our latest updates...</p></body></html>"
}

Plain Text (detected automatically if no HTML tags):

{
  "type": "email",
  "to": "user@example.com",
  "subject": "Password Reset",
  "message": "Click this link to reset your password: https://example.com/reset?token=abc123"
}

Email with Attachments

Base64-encoded attachments:

{
  "type": "email",
  "to": "user@example.com",
  "subject": "Invoice #12345",
  "message": "<p>Please find your invoice attached.</p>",
  "attachments": [
    {
      "filename": "invoice-12345.pdf",
      "content": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9...",
      "contentType": "application/pdf"
    }
  ]
}

Field Descriptions:

| Field | Type | Required | Description | |-------|------|----------|-------------| | filename | string | Yes | Attachment filename with extension | | content | string | Yes | Base64-encoded file content | | contentType | string | Yes | MIME type (e.g., "application/pdf", "image/png") |

Common MIME Types:

PDF: application/pdf
PNG: image/png
JPEG: image/jpeg
Word: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Excel: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
CSV: text/csv
Text: text/plain

Creating Base64 Attachments

JavaScript (Browser):

function fileToBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const base64 = reader.result.split(',')[1]; // Remove data:mime;base64, prefix
      resolve(base64);
    };
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}
 
// Usage
const fileInput = document.getElementById('attachment');
const file = fileInput.files[0];
const base64Content = await fileToBase64(file);
 
await fetch('https://api.notifyhub.com/notifications/send', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    type: 'email',
    to: 'user@example.com',
    subject: 'Document Attached',
    message: '<p>Please review the attached document.</p>',
    attachments: [{
      filename: file.name,
      content: base64Content,
      contentType: file.type
    }]
  })
});

Node.js:

const fs = require('fs');
 
function fileToBase64(filePath) {
  const file = fs.readFileSync(filePath);
  return file.toString('base64');
}
 
// Usage
const base64Content = fileToBase64('./invoice.pdf');
 
await fetch('https://api.notifyhub.com/notifications/send', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    type: 'email',
    to: 'user@example.com',
    subject: 'Invoice',
    message: '<p>Your invoice is attached.</p>',
    attachments: [{
      filename: 'invoice.pdf',
      content: base64Content,
      contentType: 'application/pdf'
    }]
  })
});

Python:

import base64
 
def file_to_base64(file_path):
    with open(file_path, 'rb') as f:
        return base64.b64encode(f.read()).decode('utf-8')
 
# Usage
base64_content = file_to_base64('invoice.pdf')
 
response = requests.post(
    'https://api.notifyhub.com/notifications/send',
    headers={'Authorization': f'Bearer {token}'},
    json={
        'type': 'email',
        'to': 'user@example.com',
        'subject': 'Invoice',
        'message': '<p>Your invoice is attached.</p>',
        'attachments': [{
            'filename': 'invoice.pdf',
            'content': base64_content,
            'contentType': 'application/pdf'
        }]
    }
)

Bulk Email

Request:

{
  "type": "email",
  "to": ["user1@example.com", "user2@example.com", "user3@example.com"],
  "subject": "Product Launch Announcement",
  "message": "<h1>New Product!</h1><p>We're excited to announce...</p>"
}

Response:

[
  { "recipient": "user1@example.com", "status": "success" },
  { "recipient": "user2@example.com", "status": "success" },
  { "recipient": "user3@example.com", "status": "failed", "error": "Invalid email" }
]

Email Templates

Transactional Email (Password Reset):

<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; }
    .container { max-width: 600px; margin: 0 auto; padding: 20px; }
    .button { background: #007bff; color: white; padding: 12px 24px; 
              text-decoration: none; border-radius: 4px; display: inline-block; }
  </style>
</head>
<body>
  <div class="container">
    <h1>Reset Your Password</h1>
    <p>You requested to reset your password. Click the button below:</p>
    <a href="https://example.com/reset?token=abc123" class="button">Reset Password</a>
    <p>This link expires in 1 hour.</p>
    <p>If you didn't request this, please ignore this email.</p>
  </div>
</body>
</html>

Marketing Email (Newsletter):

<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; background: #f4f4f4; }
    .container { max-width: 600px; margin: 20px auto; background: white; }
    .header { background: #007bff; color: white; padding: 20px; text-align: center; }
    .content { padding: 30px; }
    .footer { background: #333; color: white; padding: 20px; text-align: center; font-size: 12px; }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>January Newsletter</h1>
    </div>
    <div class="content">
      <h2>What's New This Month</h2>
      <p>Check out our latest features and updates...</p>
      <img src="https://example.com/banner.jpg" alt="Banner" style="max-width: 100%;">
    </div>
    <div class="footer">
      <p>© 2026 Your Company. All rights reserved.</p>
      <p><a href="https://example.com/unsubscribe" style="color: #fff;">Unsubscribe</a></p>
    </div>
  </div>
</body>
</html>

Email Best Practices

  1. Subject Lines:

    • Keep under 50 characters
    • Avoid spam trigger words (FREE, URGENT, CLICK NOW)
    • Personalize when possible ("John, your order is ready")
  2. HTML Design:

    • Use inline CSS (email clients strip <style> tags)
    • Test across email clients (Gmail, Outlook, Apple Mail)
    • Include alt text for images
    • Provide plain text fallback
  3. Deliverability:

    • Use authenticated sender domains (SPF, DKIM, DMARC)
    • Avoid spam words in subject/body
    • Include unsubscribe links for marketing emails
    • Don't send to purchased lists
  4. Accessibility:

    • Use semantic HTML (<h1>, <p>, not just <div>)
    • Sufficient color contrast
    • Descriptive link text ("Read the article" not "Click here")

WhatsApp via Meta Cloud API

Send WhatsApp messages to users who have opted in.

Basic WhatsApp Message

Request:

{
  "type": "whatsapp",
  "to": "+254712345678",
  "message": "Your order #12345 has been shipped! Track here: https://example.com/track/12345"
}

Response:

{
  "recipient": "+254712345678",
  "status": "success"
}

WhatsApp Requirements

Important Restrictions:

  1. 24-Hour Window: Free-form messages only allowed within 24 hours of user's last message
  2. Templates Required: Outside 24-hour window, must use pre-approved templates
  3. Opt-In Required: Users must explicitly opt-in to receive messages
  4. No Marketing: Unless using approved marketing templates

Phone Number Format

Same as SMS:

  • International format with country code
  • Example: +254712345678

Template Messages (Future)

For messages outside 24-hour window:

{
  "type": "whatsapp",
  "to": "+254712345678",
  "template": {
    "name": "order_update",
    "language": "en",
    "parameters": [
      { "type": "text", "text": "12345" },
      { "type": "text", "text": "January 30" }
    ]
  }
}

Note: Templates must be pre-approved by Meta. Contact support to set up templates.

WhatsApp Use Cases

Order Updates:

{
  "type": "whatsapp",
  "to": "+254712345678",
  "message": "Hi John! Your order #12345 has been delivered. Thank you for shopping with us!"
}

Customer Support:

{
  "type": "whatsapp",
  "to": "+254712345678",
  "message": "Thank you for contacting support. Your ticket #6789 has been created. We'll respond within 24 hours."
}

Appointment Confirmations:

{
  "type": "whatsapp",
  "to": "+254712345678",
  "message": "Your appointment is confirmed for January 30 at 2:00 PM. See you then! Reply CANCEL to cancel."
}

Complete WhatsApp Example

async function sendWhatsApp(to, message, token) {
  try {
    const response = await fetch('https://api.notifyhub.com/notifications/send', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        type: 'whatsapp',
        to,
        message
      })
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    }
    
    const result = await response.json();
    console.log('WhatsApp sent:', result);
    return result;
  } catch (error) {
    console.error('WhatsApp error:', error);
    throw error;
  }
}
 
// Usage
await sendWhatsApp(
  '+254712345678',
  'Your order has shipped! Track: https://track.me/12345',
  token
);

Multi-Channel Messaging

Send the same message across multiple channels (requires separate requests per channel).

Email + SMS Combination

async function sendMultiChannel(recipient, token) {
  // Send email
  await fetch('https://api.notifyhub.com/notifications/send', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type: 'email',
      to: recipient.email,
      subject: 'Order Confirmation',
      message: '<h1>Thank you!</h1><p>Your order #12345 is confirmed.</p>'
    })
  });
  
  // Send SMS
  await fetch('https://api.notifyhub.com/notifications/send', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type: 'sms',
      to: recipient.phone,
      message: 'Order #12345 confirmed! Check your email for details.'
    })
  });
}
 
// Usage
await sendMultiChannel({
  email: 'user@example.com',
  phone: '+254712345678'
}, token);

Note: For advanced multi-channel workflows, use Campaigns.


Send to All Contacts

Send a message to every contact in your organization.

Endpoint: POST https://api.notifyhub.com/notifications/send-to-all

Request:

{
  "type": "sms",
  "to": "all",
  "message": "Important announcement: Our office will be closed on Monday for maintenance."
}

Response:

[
  { "recipient": "+254712345678", "status": "success" },
  { "recipient": "+254723456789", "status": "success" },
  { "recipient": "user@example.com", "status": "failed", "error": "No phone number" }
]

Note: Only sends to contacts with the required channel (phone for SMS/WhatsApp, email for Email).


Error Handling

Common Errors

Invalid Phone Number:

{
  "statusCode": 400,
  "message": "Invalid phone number format",
  "error": "Bad Request"
}

Invalid Email:

{
  "statusCode": 400,
  "message": "Invalid email address",
  "error": "Bad Request"
}

Missing Subject (Email):

{
  "statusCode": 400,
  "message": "Subject is required for email",
  "error": "Bad Request"
}

Channel Error:

{
  "recipient": "+254712345678",
  "status": "failed",
  "error": "Invalid shortcode"
}

Error Handling Example

async function sendWithRetry(payload, token, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch('https://api.notifyhub.com/notifications/send', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
      });
      
      if (response.ok) {
        return await response.json();
      }
      
      const error = await response.json();
      
      // Don't retry on client errors
      if (response.status >= 400 && response.status < 500) {
        throw new Error(error.message);
      }
      
      // Retry on server errors
      console.log(`Attempt ${i + 1} failed, retrying...`);
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
      
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }
}

Best Practices

1. Recipient Validation

Always validate before sending:

function validateRecipient(type, to) {
  if (type === 'email') {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(to);
  }
  if (type === 'sms' || type === 'whatsapp') {
    return /^\+?\d{10,15}$/.test(to.replace(/[-\s()]/g, ''));
  }
  return false;
}

2. Rate Limiting

Implement your own rate limiting to avoid overwhelming the API:

async function sendBatch(recipients, message, token, batchSize = 100) {
  for (let i = 0; i < recipients.length; i += batchSize) {
    const batch = recipients.slice(i, i + batchSize);
    
    await fetch('https://api.notifyhub.com/notifications/send', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        type: 'sms',
        to: batch,
        message
      })
    });
    
    // Wait between batches
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}

3. Message Personalization

Use contact data for personalized messages:

async function sendPersonalized(contacts, token) {
  for (const contact of contacts) {
    const message = `Hi ${contact.name}, your order #${contact.orderId} is ready!`;
    
    await fetch('https://api.notifyhub.com/notifications/send', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        type: 'sms',
        to: contact.phone,
        message
      })
    });
  }
}

Next Steps: