Your first campaign in an hour

Key, sender, list, send, receipt. The whole loop end to end, with the mistakes that cost a day.

August 2026
Updated
9 min read
Length
A terminal window slab floating above a grid plane with a glowing block cursor

What you will take away

  • 1Get a send and a receipt working before you touch a list of any size
  • 2Idempotency keys are the cheapest insurance you will ever buy
  • 3Sender registration, not code, is what decides your launch date

An hour is realistic for a first campaign, but only if you do the steps in the right order. Almost every team that takes a week does the same thing: they build the whole list pipeline first, then discover on the day of launch that the sender is not registered in one of their markets. Get one message to one handset first. Everything else is easier afterwards.

Take a sandbox key

Keys are scoped two ways: to an environment, and to a set of route classes. A sandbox key can call every endpoint but never reaches a real network. Scope it narrowly now and widen it later, because a key that can only send transactional traffic cannot be turned into a marketing blast by accident.

what a key looks like

mr_sbx_7c41ad9e...   sandbox, all routes
mr_live_08b2f3c1...  production, transactional + marketing

Put it in an environment variable. There is no session and no refresh token, so the key is the only credential in play and it must never reach a client bundle or a repository.

Reserve the sender

Every message needs a sender the destination network recognises. In sandbox you can reserve any alphanumeric string instantly. In production the same call submits a registration to each network in the market, and that is the step that actually gates a launch. Start it on day one, not on the day you are ready.

POST /v1/senders

curl https://api.messagereach.net/v1/senders \
  -H "Authorization: Bearer $MR_KEY" \
  -d sender="LUXPLAY" \
  -d market="CY" \
  -d type="alphanumeric"

Write the template, not the message

Templates render server side with locale fallback and give you a stable identifier to report against later. A raw body works, but you lose per-market character-set handling and you lose the ability to see which version of the copy performed.

template body

Hi {{first_name}}, {{spins}} free spins are on your
account until {{expires}}. Play at {{link}}

18+. Play responsibly. Reply STOP to opt out.

Put the opt-out in the template, not in your sending code. Networks in most regulated markets check for it, and a template is the only place it cannot be forgotten.

Send one message

One destination, one template reference, one idempotency key. Read the response before you go anywhere near a list.

POST /v1/messages

curl https://api.messagereach.net/v1/messages \
  -H "Authorization: Bearer $MR_KEY" \
  -H "Idempotency-Key: first-campaign-001" \
  -d sender="LUXPLAY" \
  -d to="+35799xxxxxx" \
  -d template="free_spins_v4" \
  -d channel="auto"

{
  "id": "msg_01K4TQ8V",
  "state": "queued",
  "channel": "sms",
  "route": "CY-direct-02",
  "segments": 1,
  "eta_ms": 180
}

Always send an idempotency key. It is honoured for twenty four hours, which means a retried request after a timeout returns the original message instead of sending a second one. During an incident this is the difference between a delay and messaging thirty thousand people twice.

Read the receipt

The response above says the gateway accepted the message. It is not proof of delivery. State moves through queued, submitted, then either delivered or failed with a carrier reason attached. Poll while you are learning, then move to events before you scale.

GET /v1/messages/msg_01K4TQ8V

{
  "id": "msg_01K4TQ8V",
  "state": "delivered",
  "channel": "sms",
  "segments": 1,
  "timeline": [
    { "state": "queued",    "at": "2026-08-21T19:35:02Z" },
    { "state": "submitted", "at": "2026-08-21T19:35:02Z" },
    { "state": "delivered", "at": "2026-08-21T19:35:07Z" }
  ]
}

Only now, the list

With one delivered message behind you, a campaign is the same call in a loop with a consent check in front of it. The gateway checks consent anyway and will refuse the send, but checking first turns a rejected batch into a clean segment.

  • Filter your audience against the consent endpoint before you build the batch.
  • Derive the idempotency key from something stable in your own system, such as a campaign id plus a player id.
  • Send in batches you can reason about. A thousand at a time is plenty to spot a problem early.
  • Watch the first hundred receipts before releasing the rest. Reason codes tell you fast if a template is being filtered.

Moving to production

Swap the key and keep the code. Sandbox and production share one schema, one set of error codes and one state machine, so the only differences you meet are real ones: registration status, market licensing, and the fact that carriers occasionally take their time. Before you switch, check that your webhook consumer returns a 2xx in under two seconds and is safe to call twice, and that the markets on your licence are attached to the key. Sends outside them are refused with a 409.