Need An Online Store? Hire A Developer Business Legal Documents Better Images/Video Want More Sales?
Need An Online Store? Want More Sales?

SMS Campaign Engine - Documentation

Setup guide and full API reference for the open source, self-hosted SMS Campaign Engine.
Get the code on GitHub or read the SMS Broadcast overview first.

In all URLs below, replace yourdomain.com with the domain where you installed the engine. Prefer a managed version? The hosted SMS Broadcast app runs this same engine for you, contact our team to get set up.

What the Engine Is

One PHP app, one SQLite file, 12 SMS providers - read this first
Show / Hide
SMS Campaign Engine is a self-hosted SMS campaign system: bulk broadcasts, drip autoresponder series, delivery webhooks, click tracking, and unsubscribe management, with 12 SMS providers supported through one send API. It is one small PHP application storing everything in a single SQLite file, with no framework and no dependencies. The source is MIT licensed at github.com/AIAppsAPI/SMS-Campaign-Engine.

Requirements: PHP 8.1 or newer with the curl and pdo_sqlite extensions, both included in standard PHP builds. It runs on shared hosting, a VPS, or Docker.

The pieces:

FileWhat it does
public/api.phpThe JSON API. Every endpoint documented below lives here.
public/webhook.phpProvider callbacks for delivery reports and incoming replies.
public/click.phpTracked click redirect with bot filtering.
public/admin.phpPassword protected admin area: numbers, messages, lists, series, contacts, reports, settings.
cli/cron.phpScheduled sending entry point, run by cron or the Docker cron container.
config.phpCopied from config.sample.php, holds your keys and settings.
data/The SQLite database and queue files, created automatically.

Quick Start with Docker

Two commands, cron included
Show / Hide
cp config.sample.php config.php # edit config.php: set apiKey, adminPassword, webhookKey, publicUrl docker compose up --build

The app is then at http://localhost:8080/ with the admin area at admin.php. The compose file includes a second container that loops the cron script, so scheduled sending works out of the box with no host crontab.

Quick Start on Shared Hosting or a Server

Upload, point the docroot, add one cron line
Show / Hide

For a quick local test:

cp config.sample.php config.php # edit config.php: set apiKey, adminPassword, webhookKey, publicUrl php -S localhost:8080 -t public

On shared hosting, upload the project and point your docroot (or a subdomain) at the public/ folder. The SQLite database is created automatically in data/. Then add the cron job from the next section.

Before going live: set apiKey, adminPassword, and webhookKey to long random strings and serve over HTTPS.

Cron and Scheduled Sending

One crontab line runs everything on time
Show / Hide

Everything scheduled goes out through cli/cron.php. Add one crontab line:

*/10 * * * * php /path/to/SMS-Campaign-Engine/cli/cron.php

Every 10 minutes keeps responders on time, hourly also works. The script is safe to run as often as you like: responder steps send once, each broadcast hour bucket sends once, and the auto scheduler runs once per day. Docker users can skip this, the compose cron container already loops it.

Each cron run does three things:

StepWhat happens
1Sends autoresponder messages that are due.
2Sends the current hour's bucket of today's broadcasts.
3Once per day, when the auto scheduler is on in the admin settings, builds today's broadcasts from your lists.

Setup Walkthrough

From empty install to first broadcast
Show / Hide
StepWhat to do
1In config.php set apiKey, adminPassword, webhookKey, and publicUrl.
2Log in to admin.php and add a sending number with your provider and API key (formats in the provider section below).
3Point your provider's webhooks (incoming messages and delivery reports) at your webhook URL (see the webhook section below).
4Create a message. Placeholders: ##FNAME## first name, ##SUBID## click tracking id, ##DOMAIN## the number's sending domain, ##INCOMING## the reply-to number. A tracked link looks like https://yourdomain.com/click.php/##SUBID##.
5Upload contacts in the admin area or push them through the API.
6Create a list (sending profile) and schedule a broadcast from the dashboard, or enable the auto scheduler in settings and check the message's weekday boxes.
7For drips, create a series and subscribe phones through the API, an upload, or your signup form.

Step by step versions of the common jobs: sending a bulk broadcast, setting up a drip series, connecting a provider, and AI auto replies.

Provider API Key Formats

The api_key value to enter for each of the 12 providers
Show / Hide
When you add a sending number in the admin area, you pick its provider and paste an API key. Several providers combine two or more credentials into one value separated by colons, exactly as shown below.
Providerapi_key value
twilioAccountSid:AuthToken
telnyxAPI key
vonageapi_key:api_secret
plivoauth_id:auth_token
sinchservice_plan_id:api_token
bandwidthusername:password:accountId:applicationId
clicksendusername:api_key
messagebirdAccess key
textmagicusername:api_key
clickbuzzAPI key
voltBearer token
ahoiAPI key

Provider Webhooks

Delivery reports and incoming replies
Show / Hide
Point each provider's delivery report URL and incoming message URL at your install's webhook endpoint. If the provider has separate fields for each type, use the same URL in both. Both JSON and form-encoded callbacks are accepted. Incoming STOP replies unsubscribe the phone automatically.
POST Webhook URL pattern
https://yourdomain.com/webhook.php/{provider}?key=YOURWEBHOOKKEY

where {provider} is one of: twilio, telnyx, vonage, plivo, sinch, bandwidth, clicksend, messagebird, textmagic, clickbuzz, volt, ahoi. The key is the webhookKey from your config.php.

Delivery callbacks record delivered and undelivered counts and learn each phone's carrier as reports come in. Incoming replies are checked for STOP, and a sending number can instead route replies to the chatbot hook for automatic answers, see AI text message auto replies.

How the API Works

Authentication and request format - read before the endpoints
Show / Hide
All endpoints are POST with a JSON body. Send your key in the X-API-Key header, or as an apiKey field in the JSON body. Endpoint paths use path info, and if your server does not support that, api.php?action=send works the same as api.php/send.
POST Example request
curl -X POST https://yourdomain.com/api.php/send \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_KEY" \ -d '{"phone": "5551234567", "sendingNumber": "8885551234", "message": "Hello from the engine"}'
Carrier values used throughout this API: tmobile, att, verizon, uscellular, boost, other. Always use these exact lowercase values. If you do not know the carrier, pass other.

Send a Single SMS Message

api.php/send
Show / Hide
Sends one SMS right now. The sending number you specify must already be configured in the admin area with a valid provider and API key. For sending to a whole list, use the broadcast endpoints or the dashboard instead.
POST Endpoint
https://yourdomain.com/api.php/send
Required Fields
Field NameField KeyNotes
Phone Numberphone10-digit US phone number, no plus sign, no country code. Example: 5551234567
Sending NumbersendingNumberThe number the message comes from. Must match a configured sending number. 10-digit format.
MessagemessageThe text to send. Standard SMS length limits apply (160 characters for a single segment), longer messages send as multi-part.

Add and Upload Contacts

api.php/contacts/add | api.php/contacts/upload
Show / Hide
contacts/add adds one contact, wire it to your signup form. contacts/upload bulk imports many contacts in one call. Contacts are stored with full profile information and organized by carrier for routing and caps.
POST Add one contact
https://yourdomain.com/api.php/contacts/add
Required Fields
Field NameField KeyNotes
Phone Numberphone10-digit US phone number.
Optional Fields
Field NameField KeyNotes
Email AddressemailContact email address.
First NamefnameFirst name only, used by the ##FNAME## placeholder.
Last NamelnameLast name only.
Feed NamefeedA short code identifying which data source or campaign this contact came from. Used for tracking and filtering. Example: webform-jan
StatestateTwo-letter US state code. Example: NY, FL
CountrycountryTwo-letter country code. Example: US
CarriercarrierOne of the carrier values from the API basics section. Use other if unknown.
Signup URLsourceurlThe page where this contact opted in.
Opt-In DateoptinDate and time the contact opted in. Example: 05-01-2025 05:45:00
IP AddressipIPv4 or IPv6 address at the time of opt-in.

POST Bulk import
https://yourdomain.com/api.php/contacts/upload
Required Fields
Field NameField KeyNotes
ContentcontentOne record per line as email,sourceurl,optin,phone,fname,country,state,ip,feed,carrier,unused,lname. Only phone is required, keep the commas for skipped fields.

Drip Series (Responders)

api.php/responder/create | responder/unsub | responders/upload
Show / Hide
A responder series is a drip sequence: each subscriber gets their own independent schedule starting from when they enrolled, on the daily, weekly, or monthly rhythm you configured for the series in the admin area. Quiet hours and skip days are respected automatically. See the drip campaign setup guide for the full walkthrough.
POST Subscribe a phone to a series
https://yourdomain.com/api.php/responder/create
Required Fields
Field NameField KeyNotes
Phone Numberphone10-digit US phone number to enroll.
Series IDseriesThe ID of the responder series, created and named in the admin area.
Optional Fields
Field NameField KeyNotes
Start TimestarttimeUnix timestamp for when the first message should go out. When the subscriber's local time of day allows it, message 0 goes out immediately.
First NamefnameUsed by the ##FNAME## placeholder.
Feed NamefeedTracking tag identifying the source of this enrollment.
CarriercarrierOne of the carrier values from the API basics section.
Exclude CarriersexcludeCarriersComma-separated carriers to skip. If the subscriber's carrier is in this list, enrollment is silently skipped. Example: verizon,tmobile

POST Remove a phone from a series
https://yourdomain.com/api.php/responder/unsub
Field NameField KeyNotes
Phone NumberphoneREQUIRED The phone to remove.
Series IDseriesOPTIONAL Remove from this one series. If omitted, the phone is removed from all series. This does not touch the global suppression list, use the unsubscribe endpoint for that.

POST Bulk subscribe
https://yourdomain.com/api.php/responders/upload
Field NameField KeyNotes
ContentcontentREQUIRED Contact lines in the same format as contacts/upload.
Series IDseriesREQUIRED The series to enroll every line into.

Unsubscribes and Suppression

api.php/unsub | api.php/unsubs/upload
Show / Hide
Unsubscribing a phone removes it from broadcasts and all series and adds it to the suppression list, which survives re-imports. Incoming STOP replies are handled automatically by the webhook, these endpoints are for unsubscribes coming from your own systems or bulk files.
POST Unsubscribe one phone from everything
https://yourdomain.com/api.php/unsub
Field NameField KeyNotes
Phone NumberphoneREQUIRED 10-digit US phone number to suppress.

POST Bulk unsubscribe
https://yourdomain.com/api.php/unsubs/upload
Field NameField KeyNotes
ContentcontentREQUIRED One phone number per line.

Broadcast Scheduling

api.php/broadcast/schedule | broadcast/autoschedule | broadcast/dataquery
Show / Hide
broadcast/schedule queues a broadcast for today: the engine fills the queue from the list, engaged contacts first, and spreads sending across hour buckets which cron sends as each hour comes up. broadcast/autoschedule runs the auto scheduler immediately instead of waiting for the daily cron pass. broadcast/dataquery rebuilds the queue files and activity counts from the contact table, and runs automatically before scheduling when the data is older than a day. See how to send bulk SMS for the full walkthrough.
POST Queue a broadcast for today
https://yourdomain.com/api.php/broadcast/schedule
Required Fields
Field NameField KeyNotes
List IDlistIDThe list (sending profile) to send to.
Message IDofferidThe ID of the message to send.
Total To SendtotalToSendHow many messages to send today. The queue spreads them across hour buckets through the day.
Optional Fields
Field NameField KeyNotes
Message OverridemessageMessage text to use instead of the stored message content.
Redirect OverrideredirectUrlClick redirect URL to use instead of the message's stored redirect.

POST Run the auto scheduler now
https://yourdomain.com/api.php/broadcast/autoschedule

No fields. Builds today's broadcasts from your lists, one per list, rotating through the messages enabled for today's weekday.


POST Rebuild queue data
https://yourdomain.com/api.php/broadcast/dataquery

No fields. Rebuilds the queue files and activity counts from the contact table.

Conversions and Stats

api.php/convert | api.php/stats
Show / Hide
convert marks a phone as a converter so it gets top priority in future broadcasts, call it from your checkout or lead endpoint. stats returns the day's numbers for your reporting.
POST Mark a conversion
https://yourdomain.com/api.php/convert
Field NameField KeyNotes
Phone NumberphoneREQUIRED The phone that converted.

POST Daily stats
https://yourdomain.com/api.php/stats
Field NameField KeyNotes
DatedateOPTIONAL Day to report as m-d-Y, defaults to today. Returns delivered, undelivered, clicks, unsubs, the activity counts, and list sizes.

Click Tracking and Bot Filtering

click.php and the ##SUBID## placeholder
Show / Hide
Put https://yourdomain.com/click.php/##SUBID## in your message text. The engine fills ##SUBID## per recipient, and the click redirects to the message's redirect URL with ##SUBID## substituted there too, so your landing page receives the tracking id.

Clicks count toward the contact's activity level, upgrading them to clicker so they get priority in future broadcasts. Known bots are sent to the defaultRedirect from config.php and are not counted. Setting botDetectProvider in config.php and enabling the setting also asks an LLM about suspicious user agents before counting the click.

Compliance Notes

Opt-ins, 10DLC registration, STOP language
Show / Hide
Rule
Only message people who opted in, and honor opt-outs immediately. The engine's STOP handling, suppression list, and unsub uploads are there to keep you clean, use them.
US carriers require registered campaigns (10DLC, toll-free verification, or short codes) for application traffic. Register your numbers with your provider before sending volume. See the 10DLC registration guide.
Include opt-out language, like "Reply STOP to opt out", in your messages. The TCPA compliance guide covers the consent rules in depth.
Set apiKey, adminPassword, and webhookKey to long random strings and serve over HTTPS before going live.