smsdora
← Back to homepage
API Documentation

smsdora Integration Guide

Everything you need to send SMS programmatically through your connected Android devices.

POST/messages/send

Overview

smsdorais a device-based SMS gateway that lets you send SMS messages through real Android devices. Instead of relying on expensive SMS provider APIs, you use your own phone(s) as the sending infrastructure — paying only your carrier's standard SMS rates.

Here's the idea in a nutshell:

  • You install our Android app on a phone and keep it connected.
  • You create an API key from the dashboard.
  • Your application calls our API endpoint with the recipient number and message.
  • The backend dispatches the SMS job to your phone, which sends it natively and reports the delivery status back.

Getting Started

Send your first SMS in under 5 minutes:

Step 1

Create an Account

Sign up at smsfor.me/signup with your email or Google account.

Step 2

Connect Your Android Phone

Install the smsdora app from the Play Store. Log in with your account and grant SMS permissions. Your device will appear in the dashboard automatically.

Step 3

Create an API Key

Go to Dashboard → API Keys and create a new key. Make sure the messages:send scope is enabled. Copy and save the key — it's only shown once.

Step 4

Send Your First SMS

Make an API call to our send endpoint:

Send SMSbash
curl -X POST https://api.smsdora.com/messages/send \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": "+2348034125567",
    "message": "Hello from smsdora!"
  }'

API Keys

API keys are how your application authenticates with smsdora. You create them from the API Keys page in the dashboard.

Store Your Key Securely

The full API key is only shown oncewhen you create it. We store a hashed version — we can't retrieve the original for you. Treat it like a password.

How to Use Your API Key

Include the key in the request header using either method:

Option 1 — x-api-key header (recommended)http
x-api-key: smsgw_ab12cd34_e7f8a9b0c1d2e3f4...
Option 2 — Authorization headerhttp
Authorization: Bearer smsgw_ab12cd34_e7f8a9b0c1d2e3f4...

Scopes

Each API key carries one or more scopes that control what it can do. Grant only what your integration needs — you choose the scopes when creating the key.

ScopeGrants
messages:sendSend new SMS and resend failed messages
messages:readList and read your message logs
devices:readList and read your connected devices

Key Features

  • Rate limiting — You can set a per-minute request limit per key
  • Expiration — Optionally set an expiry date for automatic deactivation
  • Revoke — Instantly deactivate a key if compromised
  • Rotate — Generate a new secret while keeping the same key settings

Never use API keys in client-side code

Don't embed your API key in mobile apps, browser JavaScript, or any public-facing code. Always make API calls from your backend server to keep the key secret.

Sending SMS

Send an SMS with a single POST request:

POSThttps://api.smsdora.com/messages/send

Headers

HeaderValue
x-api-keyYour API key
Content-Typeapplication/json

Request Body

FieldTypeRequiredDescription
recipientstringYesPhone number in E.164 format (e.g. +2348034125567)
messagestringYesSMS text content (1–1600 characters)

Example Request

cURLbash
curl -X POST https://api.smsdora.com/messages/send \
  -H "x-api-key: smsgw_ab12cd34_e7f8a9b0c1d2e3f4..." \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": "+212600000000",
    "message": "Your verification code is 4829"
  }'

Success Response

201 Createdjson
{
  "statusCode": 201,
  "message": "SMS queued for delivery",
  "data": {
    "id": "64a...",
    "recipient": "+212600000000",
    "message": "Your verification code is 4829",
    "status": "dispatching",
    "device": {
      "id": "64b...",
      "deviceName": "My Samsung S24"
    }
  }
}

Automatic device selection

You don't need to specify which phone sends the SMS. The backend automatically picks the best available device from your account — one that is online, active, and most recently seen.

Message Status

After sending, your message progresses through these statuses:

queueddispatchingsendingsentdelivered
StatusWhat it means
queuedMessage received by our backend
dispatchingJob sent to your Android device via push notification
sendingYour phone is actively sending the SMS
sentSMS handed off to the carrier network
deliveredCarrier confirmed delivery to the recipient
failedSMS could not be sent or delivered

You can track the status of any message from the Messages page in the dashboard — or fetch it programmatically with the Reading Messages endpoints below.

Reading Messages

Pull your message history programmatically to track delivery, reconcile orders, or build your own reporting. These endpoints require an API key with the messages:read scope. Results are always scoped to the key's owner.

GEThttps://api.smsdora.com/messages

Query Parameters

All parameters are optional. Results are sorted newest first.

ParameterDescription
statusFilter by status: queued, dispatching, sending, sent, delivered, failed
recipientPartial, case-insensitive match on the recipient number
deviceIdOnly messages sent from this device
sourceapi, dashboard, manual, system, or campaign
from / toISO date range filter on creation time
pagePage number (default 1)
limitResults per page (default 20, max 100)

Example Request

cURLbash
curl "https://api.smsdora.com/messages?status=failed&limit=20" \
  -H "x-api-key: YOUR_API_KEY"

Success Response

200 OKjson
{
  "statusCode": 200,
  "data": {
    "messages": [ /* array of message objects (see below) */ ],
    "pagination": {
      "total": 42,
      "page": 1,
      "limit": 20,
      "totalPages": 3
    }
  }
}

Get a Single Message

GEThttps://api.smsdora.com/messages/:id
200 OKjson
{
  "statusCode": 200,
  "data": {
    "id": "64a...",
    "deviceId": "64b...",
    "campaignId": null,
    "resentFromMessageId": null,
    "resentToMessageId": null,
    "recipient": "+212600000000",
    "message": "Your verification code is 4829",
    "segmentsCount": 1,
    "status": "delivered",
    "failureReason": null,
    "provider": "android_device",
    "source": "api",
    "externalRequestId": "order-123",
    "queuedAt": "2026-06-16T10:00:00.000Z",
    "dispatchedAt": "2026-06-16T10:00:01.000Z",
    "sendingAt": "2026-06-16T10:00:02.000Z",
    "sentAt": "2026-06-16T10:00:03.000Z",
    "deliveredAt": "2026-06-16T10:00:09.000Z",
    "failedAt": null,
    "createdAt": "2026-06-16T10:00:00.000Z",
    "updatedAt": "2026-06-16T10:00:09.000Z"
  }
}

Message Object Fields

FieldDescription
idUnique message identifier
recipientDestination phone number
messageSMS text content
segmentsCountNumber of SMS segments the text was split into
statusCurrent delivery status
failureReasonWhy the message failed, when applicable
deviceIdDevice that sent (or will send) the message
sourceOrigin of the message (api, dashboard, etc.)
externalRequestIdYour own correlation id, if provided when sending
resentFromMessageIdOn a resent message — points to the original failed message
resentToMessageIdOn an original message once resent — points to the new message
*At timestampsqueuedAt, dispatchedAt, sendingAt, sentAt, deliveredAt, failedAt, plus createdAt / updatedAt

Resend a Failed SMS

If a message fails to send, you can resend it without rebuilding the request. Resending creates a brand-new message that reuses the original's recipient, text, device, and externalRequestId. Requires the messages:send scope.

POSThttps://api.smsdora.com/messages/:id/resend

Only failed messages can be resent

The message referenced by :id must currently have a failed status. Resending a queued, sent, or delivered message returns 409 Conflict.

Example Request

cURLbash
curl -X POST https://api.smsdora.com/messages/MESSAGE_ID/resend \
  -H "x-api-key: YOUR_API_KEY"

Success Response

201 Createdjson
{
  "statusCode": 201,
  "message": "SMS queued for delivery",
  "data": {
    "id": "<new message id>",
    "resentFromMessageId": "<original failed message id>",
    "resentToMessageId": null,
    "recipient": "+212600000000",
    "message": "Your verification code is 4829",
    "status": "dispatching",
    "source": "api",
    "externalRequestId": "order-123",
    "device": {
      "id": "64b...",
      "deviceName": "My Samsung S24",
      "phoneNumber": "+212600000000"
    }
  }
}

The original is preserved

The original failed message is left untouched except for a resentToMessageId link pointing to the new message, so you always keep a full audit trail.

Devices

List the Android devices connected to your account and check whether they are online before sending. Requires an API key with the devices:read scope.

GEThttps://api.smsdora.com/devices

Example Request

cURLbash
curl https://api.smsdora.com/devices \
  -H "x-api-key: YOUR_API_KEY"

Success Response

200 OKjson
{
  "statusCode": 200,
  "data": [
    {
      "id": "64b...",
      "deviceId": "android-abc123",
      "deviceName": "My Samsung S24",
      "platform": "android",
      "brand": "Samsung",
      "model": "SM-S921B",
      "isActive": true,
      "status": "online",
      "batteryLevel": 87,
      "isCharging": false,
      "simLabel": "Orange MA",
      "simSlot": 1,
      "lastSeenAt": "2026-06-16T10:00:00.000Z"
    }
  ]
}

Get a Single Device

GEThttps://api.smsdora.com/devices/:id

Returns fuller device metadata, including phoneNumber, androidVersion, and appVersion.

Secrets are never exposed to API clients

Device responses for API-key clients never include sensitive fields such as the device key or the push token (fcmToken). Only safe, read-only device metadata is returned.

OTP Authentication

Send and verify one-time passcodes over SMS with the official @smsdora/otp Node.js SDK. It wraps the OTP endpoints with built-in retries, idempotent sends, typed errors, and local verification-token validation. Requires an API key with the otp scope.

Server-side only

The SDK holds your secret API key — never ship it to a browser or mobile app. For client apps, use a SmsDora publishable key, which is restricted to the otp scope.

Install

Terminalbash
npm install @smsdora/otp

Zero runtime dependencies (built-in fetch + node:crypto). Ships ESM and CommonJS with full TypeScript types. Requires Node.js 18 or later.

Quick Start

server.tstypescript
import { SmsdoraOtp } from '@smsdora/otp';

const otp = new SmsdoraOtp({
  apiKey: process.env.SMSDORA_SECRET_KEY!,
  baseUrl: 'https://api.smsdora.com',
  tokenSecret: process.env.OTP_TOKEN_SECRET,
});

// 1. Send a code
const challenge = await otp.send({
  recipient: '+2348031234567',
  purpose: 'login',
});

// 2. Verify what the user typed
const result = await otp.verify({
  challengeId: challenge.id,
  code: '123456',
});

if (result.verified) {
  const claims = otp.verifyToken(result.verificationToken!);
  console.log('verified phone:', claims.sub, 'for', claims.purpose);
}

Client Options

Pass these to new SmsdoraOtp(options):

OptionTypeDefaultNotes
apiKeystringRequired. Secret key with the otp scope.
baseUrlstringRequired. Your SmsDora API base URL.
tokenSecretstringShared OTP_TOKEN_SECRET; needed for verifyToken().
timeoutMsnumber10000Per-request timeout.
maxRetriesnumber2Retries on network / 5xx / 429.
fetchtypeof fetchglobalCustom fetch implementation.

Sending a Code

send(params) returns a Promise<OtpChallenge>. Only recipient is required — the rest are optional.

otp.send()typescript
await otp.send({
  recipient: '+2348031234567',
  purpose: 'login',
  codeLength: 6,
  ttlSeconds: 300,
  templateOverride: 'Your code is {{code}} ({{ttl}} min)',
  metadata: { userId: 'u_123' },
  idempotencyKey: 'order-42',
});

Verifying a Code

verify(params) resolves with { verified, status, attemptsRemaining, verificationToken? }. A wrong code resolves with verified: false — it does not throw. When verification succeeds, validate the returned token locally with verifyToken().

otp.verify()typescript
const result = await otp.verify({
  challengeId: challenge.id,
  code: '123456',
});

// result.verified          -> boolean
// result.status            -> challenge status
// result.attemptsRemaining -> number
// result.verificationToken -> string (present when verified)

Other Methods

  • resend({ challengeId }) — re-delivers a fresh code for a pending challenge. Respects the resend cooldown and cap.
  • getStatus(challengeId) — returns the current OtpChallenge.
  • verifyToken(token) — validates a verification token locally (HS256, no network). Requires tokenSecret.
  • verifyTokenRemote(token) — validates on the server with single-use enforcement.

Error Handling

Failed calls throw typed errors you can catch by class. A wrong code is not an error — only delivery, auth, rate-limit, and network problems throw.

Typed errorstypescript
import { RateLimitError, DeliveryError, AuthError } from '@smsdora/otp';

try {
  await otp.send({ recipient });
} catch (err) {
  if (err instanceof RateLimitError) {
    // err.retryAfterSeconds
  } else if (err instanceof DeliveryError) {
    // no online device / FCM failure
  } else if (err instanceof AuthError) {
    // bad key / missing scope
  }
}
ClasscodeWhen
ConfigErrorconfig_errorMissing apiKey / baseUrl / tokenSecret.
ValidationErrorvalidation_error400 — bad params.
AuthErrorauth_error401 / 403.
NotFoundErrornot_found404.
RateLimitErrorrate_limited429 — has retryAfterSeconds.
DeliveryErrordelivery_failed502 — could not deliver.
ServerErrorserver_error5xx.
NetworkErrornetwork_errorTimeout / connection failure.
TokenVerificationErrortoken_invalidBad / expired verification token.

Error Handling

If something goes wrong, the API returns a JSON error with an HTTP status code:

Error Responsejson
{
  "statusCode": 400,
  "message": "No eligible device available",
  "error": "Bad Request"
}

Common Errors

CodeCauseWhat to do
400No eligible device availableMake sure at least one device is online and active
400Invalid phone number or messageCheck the recipient format and message length
401Invalid, expired, or revoked API keyCheck your API key or create a new one
403Rate limit exceededWait and retry, or increase your key's rate limit
403Missing required scope (e.g. messages:send, messages:read, devices:read)Create a new key with the correct scope
404Message or device not found (or not owned by your key)Check the id — you can only access your own resources
409Resending a message that is not in a failed stateOnly failed messages can be resent

Rate Limits

Each API key can have a configurable rate limit (requests per minute). You set this when creating the key in the dashboard.

  • If no rate limit is set, the key has unlimited requests per minute
  • Exceeding the limit returns 403 Forbidden
  • The rate limit resets on a rolling 60-second window

Plan-based limits

Your subscription plan also determines the monthly SMS volume and number of devices you can connect. Check the Pricing page for details.

Code Examples

Ready-to-use examples for sending an SMS. Replace YOUR_API_KEY with your actual key.

cURL

cURLbash
curl -X POST https://api.smsdora.com/messages/send \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": "+2348034125567",
    "message": "Hello from smsdora!"
  }'

JavaScript / Node.js

Node.js (fetch)javascript
const response = await fetch(
  "https://api.smsdora.com/messages/send",
  {
    method: "POST",
    headers: {
      "x-api-key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      recipient: "+2348034125567",
      message: "Hello from smsdora!",
    }),
  }
);

const data = await response.json();
console.log(data);

Python

Python (requests)python
import requests

response = requests.post(
    "https://api.smsdora.com/messages/send",
    headers={
        "x-api-key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "recipient": "+2348034125567",
        "message": "Hello from smsdora!",
    },
)

print(response.json())

PHP

PHP (cURL)php
<?php
$ch = curl_init("https://api.smsdora.com/messages/send");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "x-api-key: YOUR_API_KEY",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "recipient" => "+2348034125567",
        "message" => "Hello from smsdora!",
    ]),
]);

$response = curl_exec($ch);
curl_close($ch);

echo $response;

C# / .NET

C# (HttpClient)csharp
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY");

var payload = new {
    recipient = "+2348034125567",
    message = "Hello from smsdora!"
};

var response = await client.PostAsJsonAsync(
    "https://api.smsdora.com/messages/send",
    payload
);

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);

Frequently Asked Questions

Ready to get started?

Create your free account, connect a phone, and start sending SMS in minutes.