Skip to content

Send SMS Messages ​

Send SMS messages to recipients worldwide using YeboLink's messaging API.

Endpoint ​

POST /api/v1/messages/send

Authentication ​

Requires API Key authentication via X-API-Key header.

Request Body ​

ParameterTypeRequiredDescription
tostringYesRecipient phone number in E.164 format (e.g., +1234567890)
channelstringYesMust be "sms" for SMS messages
contentobjectYesMessage content object
content.textstringYesThe message text (max 1600 characters)
scheduled_atstringNoISO 8601 timestamp to send later instead of immediately — see Scheduling
metadataobjectNoCustom metadata for tracking (key-value pairs)

Optional Headers ​

HeaderDescription
Idempotency-KeyClient-generated key (max 255 chars) that makes this send safe to retry without double-sending or double-charging — see Idempotency

Try It Now ​

Send SMS Message

Your API key is stored locally and never sent to our servers. Get an API key →

Example Request ​

bash
curl -X POST https://api.yebolink.com/api/v1/messages/send \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ybk_your_api_key" \
  -d '{
    "to": "+1234567890",
    "channel": "sms",
    "content": {
      "text": "Hello! Your verification code is 123456. Valid for 10 minutes."
    },
    "metadata": {
      "user_id": "12345",
      "purpose": "verification"
    }
  }'
javascript
const sendSMS = async (to, message) => {
  const response = await fetch('https://api.yebolink.com/api/v1/messages/send', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.YEBOLINK_API_KEY
    },
    body: JSON.stringify({
      to,
      channel: 'sms',
      content: {
        text: message
      },
      metadata: {
        user_id: '12345',
        purpose: 'verification'
      }
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message);
  }

  return await response.json();
};

// Usage
try {
  const result = await sendSMS(
    '+1234567890',
    'Hello! Your verification code is 123456. Valid for 10 minutes.'
  );
  console.log('Message sent:', result.data.message_id);
  console.log('Credits used:', result.data.credits_used);
} catch (error) {
  console.error('Failed to send SMS:', error.message);
}
python
import os
import requests

def send_sms(to: str, message: str, metadata: dict = None):
    """Send SMS message via YeboLink"""

    api_key = os.environ.get('YEBOLINK_API_KEY')

    response = requests.post(
        'https://api.yebolink.com/api/v1/messages/send',
        headers={
            'X-API-Key': api_key
        },
        json={
            'to': to,
            'channel': 'sms',
            'content': {
                'text': message
            },
            'metadata': metadata or {}
        }
    )

    response.raise_for_status()
    return response.json()

# Usage
try:
    result = send_sms(
        to='+1234567890',
        message='Hello! Your verification code is 123456. Valid for 10 minutes.',
        metadata={
            'user_id': '12345',
            'purpose': 'verification'
        }
    )

    print(f"Message sent: {result['data']['message_id']}")
    print(f"Credits used: {result['data']['credits_used']}")
except requests.exceptions.HTTPError as e:
    print(f"Failed to send SMS: {e.response.json()['message']}")
php
<?php

function sendSMS($to, $message, $metadata = []) {
    $apiKey = getenv('YEBOLINK_API_KEY');

    $curl = curl_init();

    curl_setopt_array($curl, [
        CURLOPT_URL => 'https://api.yebolink.com/api/v1/messages/send',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            "X-API-Key: $apiKey"
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'to' => $to,
            'channel' => 'sms',
            'content' => [
                'text' => $message
            ],
            'metadata' => $metadata
        ])
    ]);

    $response = curl_exec($curl);
    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);

    curl_close($curl);

    if ($httpCode !== 201) {
        $error = json_decode($response, true);
        throw new Exception($error['message']);
    }

    return json_decode($response, true);
}

// Usage
try {
    $result = sendSMS(
        '+1234567890',
        'Hello! Your verification code is 123456. Valid for 10 minutes.',
        [
            'user_id' => '12345',
            'purpose' => 'verification'
        ]
    );

    echo "Message sent: " . $result['data']['message_id'] . "\n";
    echo "Credits used: " . $result['data']['credits_used'] . "\n";
} catch (Exception $e) {
    echo "Failed to send SMS: " . $e->getMessage() . "\n";
}
?>
ruby
require 'net/http'
require 'json'
require 'uri'

def send_sms(to, message, metadata = {})
  api_key = ENV['YEBOLINK_API_KEY']
  uri = URI('https://api.yebolink.com/api/v1/messages/send')

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri.path)
  request['Content-Type'] = 'application/json'
  request['X-API-Key'] = api_key
  request.body = {
    to: to,
    channel: 'sms',
    content: {
      text: message
    },
    metadata: metadata
  }.to_json

  response = http.request(request)

  if response.code.to_i != 201
    error = JSON.parse(response.body)
    raise "Failed to send SMS: #{error['message']}"
  end

  JSON.parse(response.body)
end

# Usage
begin
  result = send_sms(
    '+1234567890',
    'Hello! Your verification code is 123456. Valid for 10 minutes.',
    { user_id: '12345', purpose: 'verification' }
  )

  puts "Message sent: #{result['data']['message_id']}"
  puts "Credits used: #{result['data']['credits_used']}"
rescue => e
  puts "Error: #{e.message}"
end
go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
)

type SMSRequest struct {
    To       string                 `json:"to"`
    Channel  string                 `json:"channel"`
    Content  map[string]string      `json:"content"`
    Metadata map[string]string      `json:"metadata,omitempty"`
}

type SMSResponse struct {
    Success bool `json:"success"`
    Data    struct {
        MessageID   string `json:"message_id"`
        Status      string `json:"status"`
        CreditsUsed int    `json:"credits_used"`
        CreatedAt   string `json:"created_at"`
    } `json:"data"`
}

func sendSMS(to, message string, metadata map[string]string) (*SMSResponse, error) {
    apiKey := os.Getenv("YEBOLINK_API_KEY")
    url := "https://api.yebolink.com/api/v1/messages/send"

    payload := SMSRequest{
        To:      to,
        Channel: "sms",
        Content: map[string]string{
            "text": message,
        },
        Metadata: metadata,
    }

    jsonPayload, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))
    if err != nil {
        return nil, err
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-API-Key", apiKey)

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    if resp.StatusCode != 201 {
        return nil, fmt.Errorf("failed to send SMS: %s", string(body))
    }

    var result SMSResponse
    err = json.Unmarshal(body, &result)
    if err != nil {
        return nil, err
    }

    return &result, nil
}

func main() {
    result, err := sendSMS(
        "+1234567890",
        "Hello! Your verification code is 123456. Valid for 10 minutes.",
        map[string]string{
            "user_id": "12345",
            "purpose": "verification",
        },
    )

    if err != nil {
        fmt.Printf("Failed to send SMS: %v\n", err)
        return
    }

    fmt.Printf("Message sent: %s\n", result.Data.MessageID)
    fmt.Printf("Credits used: %d\n", result.Data.CreditsUsed)
}

Response ​

Success Response (201 Created) ​

json
{
  "success": true,
  "data": {
    "message_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "queued",
    "credits_used": "1.00",
    "created_at": "2025-11-02T12:00:00Z"
  }
}

Response Fields:

FieldTypeDescription
message_idstringUnique identifier for the message
statusstringCurrent message status (queued, sent, delivered, failed)
credits_usednumberNumber of credits deducted for this message
created_atstringISO 8601 timestamp when message was created

Message Statuses ​

Your SMS goes through several statuses:

StatusDescription
scheduledMessage is waiting for its scheduled_at time (scheduled sends only)
queuedMessage is queued for sending
sentMessage has been sent to the carrier
deliveredMessage was successfully delivered to recipient
failedMessage delivery failed
cancelledA scheduled message was cancelled before sending

Track status changes using webhooks for real-time updates.

Error Responses ​

Invalid Phone Number (400) ​

json
{
  "success": false,
  "error": "validation_error",
  "message": "Phone number must be in E.164 format (e.g., +1234567890)"
}

Insufficient Credits (402) ​

json
{
  "success": false,
  "error": "insufficient_credits",
  "message": "Not enough credits. You need 1 credits but have 0."
}

Invalid API Key (401) ​

json
{
  "success": false,
  "error": "unauthorized",
  "message": "Invalid or missing API key"
}

Rate Limit Exceeded (429) ​

json
{
  "success": false,
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Please try again later."
}

Phone Number Format ​

E.164 Format Required

Always use E.164 format for phone numbers: +[country code][number]

Valid Examples:

  • US: +1234567890
  • UK: +441234567890
  • South Africa: +27123456789
  • Lesotho: +26878422613

Invalid Examples:

  • 1234567890 (missing +)
  • 001234567890 (incorrect prefix)
  • +1 (234) 567-890 (contains spaces/punctuation)

Message Length & Encoding ​

GSM-7 Encoding (Standard) ​

Most English text uses GSM-7 encoding:

  • Single SMS: 160 characters
  • Concatenated SMS: 153 characters per part (7 characters used for concatenation headers)

Example:

  • 160 characters = 1 SMS = 1 credit
  • 161-306 characters = 2 SMS = 2 credits
  • 307-459 characters = 3 SMS = 3 credits

Unicode (UCS-2) Encoding ​

Messages with emojis or special characters use Unicode:

  • Single SMS: 70 characters
  • Concatenated SMS: 67 characters per part

Triggers Unicode:

  • Emojis (😀, 🎉, ❤️)
  • Special characters (Arabic, Chinese, etc.)
  • Some punctuation (", ", —)

Best Practices ​

1. Validate Phone Numbers ​

Always validate phone numbers before sending:

javascript
function isValidE164(phoneNumber) {
  const e164Regex = /^\+[1-9]\d{1,14}$/;
  return e164Regex.test(phoneNumber);
}

if (!isValidE164(phoneNumber)) {
  throw new Error('Invalid phone number format');
}

2. Handle Errors Gracefully ​

Implement retry logic with exponential backoff:

javascript
async function sendSMSWithRetry(to, message, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await sendSMS(to, message);
    } catch (error) {
      if (error.response?.status === 429) {
        // Rate limited - wait and retry
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        continue;
      }
      throw error; // Don't retry other errors
    }
  }
  throw new Error('Max retries exceeded');
}

Retrying a timeout needs an idempotency key

The example above only retries on 429, which is safe because a rate-limited request never sends. Retrying after a timeout or connection error is different — the send may have succeeded already, and a bare retry creates a second message and a second charge. Pass an Idempotency-Key header to make those retries safe.

3. Use Metadata for Tracking ​

Add metadata to track messages in your system:

javascript
await sendSMS('+1234567890', 'Your code is 123456', {
  user_id: '12345',
  purpose: 'verification',
  campaign_id: 'onboarding_2025',
  environment: 'production'
});

4. Monitor Credits ​

Check your balance regularly:

javascript
const checkCredits = async () => {
  const response = await fetch('https://api.yebolink.com/api/v1/account', {
    headers: {
      'Authorization': `Bearer ${jwt_token}`
    }
  });
  const data = await response.json();
  console.log('Credits remaining:', data.data.credits_balance);
};

5. Use Webhooks ​

Set up webhooks to receive delivery confirmations:

javascript
// Your webhook endpoint
app.post('/webhooks/yebolink', (req, res) => {
  const { event, data } = req.body;

  if (event === 'message.delivered') {
    console.log(`Message ${data.message_id} delivered to ${data.recipient}`);
    // Update your database
  } else if (event === 'message.failed') {
    console.log(`Message ${data.message_id} failed: ${data.error_message}`);
    // Handle failure
  }

  res.status(200).send('OK');
});

Use Cases ​

OTP/Verification Codes ​

javascript
const sendOTP = async (phoneNumber, code) => {
  return await sendSMS(
    phoneNumber,
    `Your verification code is ${code}. Valid for 10 minutes. Do not share this code.`,
    { purpose: 'otp', code }
  );
};

Order Notifications ​

javascript
const sendOrderConfirmation = async (phoneNumber, orderNumber) => {
  return await sendSMS(
    phoneNumber,
    `Your order #${orderNumber} has been confirmed! Track your delivery at example.com/track/${orderNumber}`,
    { purpose: 'order_confirmation', order_id: orderNumber }
  );
};

Appointment Reminders ​

javascript
const sendAppointmentReminder = async (phoneNumber, appointmentTime) => {
  return await sendSMS(
    phoneNumber,
    `Reminder: Your appointment is scheduled for ${appointmentTime}. Reply CANCEL to reschedule.`,
    { purpose: 'appointment_reminder' }
  );
};

Marketing Messages ​

javascript
const sendPromotionalSMS = async (phoneNumber, promoCode) => {
  return await sendSMS(
    phoneNumber,
    `Special offer! Get 20% off with code ${promoCode}. Valid until midnight. Shop now: example.com`,
    { purpose: 'marketing', campaign: 'flash_sale_nov' }
  );
};

Credits & Pricing ​

  • Cost: 1 credit per SMS (160 characters)
  • Concatenated Messages: Each segment costs 1 credit
  • Example: A 320-character message = 3 SMS segments = 3 credits

See Billing for credit packages and pricing.

Next Steps ​

Built with VitePress