Skip to content
MessagingSMS campaigns

Messaging

SMS campaigns

Preview, send and track SMS campaigns.

Pilotv1

SMS campaigns spend the business's SMS balance and text only customers who agreed to offers by SMS. Every message ends with a one-tap opt-out link. Sending needs sms:write, which no preset grants. The first campaign and large ones wait for an operator's review.

No answer ever carries a customer's name or phone.

Paths are relative to https://app.revisit.cards/v1/developer.

Read whether SMS is open, the balance and the price

GET/sms/access

Scopesms:read

Requires sms:read. Answers even while SMS is suspended, so the owner learns why: state is approved or suspended (with the operator's suspendedReason), paused is the operator's kill switch, available is false on a self-hosted deployment. balance is the plan's segments this cycle (pool) plus bought credit; ratePoisha is one segment's price, VAT included. A test key reads its live owner's balance and spends none of it.

Returns 200

availablebooleanRequired
balanceobjectRequired
balance.creditintegerRequired
balance.poolintegerRequired
balance.totalintegerRequired
pausedbooleanRequired
ratePoishaintegerRequired
stateenumRequired
One of approved, suspended.
suspendedReasonstring or nullRequired
Request
curl "https://app.revisit.cards/v1/developer/sms/access" \
  -H "Authorization: Bearer $DEVELOPER_API_KEY"
Response
{
  "data": {
    "available": false,
    "balance": {
      "credit": 250,
      "pool": 250,
      "total": 12
    },
    "paused": false,
    "ratePoisha": 40,
    "state": "approved",
    "suspendedReason": null
  },
  "requestId": "b4807bbf-7301-4c2f-bf37-947711fb618b",
  "success": true
}

Preview who a text would reach and what it would cost

POST/sms/campaigns/preview

Scopesms:read

Requires sms:read. Nothing is held or sent. The same preview as the composer: the exact text a customer gets (the opt-out link is a stand-in), segments, reachable customers (those who agreed to offers by SMS and have not opted out), notReachable and alreadyTexted as counts only, costPoisha, the balance on the day it would go out, missing segments, content issues, and whether it waits for an operator (review). A customer's number is never returned. A key limited to several branches or cards must name one with branchId and programId.

Body

audienceobject
audience.kindenumRequired
One of all, broadcast, segment, individual, location.
audience.segmentenum
One of lapsed_7, lapsed, one_away, reward_ready, new_week, champions.
audience.membershipIdsuuid[]
audience.branchIduuid
bodystringRequired
The shop's words. The shop name is put in front and the opt-out link after it.
branchIduuid
noWalletOnlyboolean
Only customers who have not added the card to a wallet.
programIduuid
scheduledFordate-time
Moved into the 9:00 to 21:00 Dhaka window.

Returns 200

alreadyTextedintegerRequired
balanceobjectRequired
balance.creditintegerRequired
balance.poolintegerRequired
balance.totalintegerRequired
charactersLeftintegerRequired
costPoishaintegerRequired
encodingenumRequired
One of GSM_7BIT, UCS2.
issuesobject[]Required
issues[].codeenumRequired
One of banned_topic, empty, link, no_bangla, too_long.
issues[].messagestringRequired
missingintegerRequired
notReachableintegerRequired
opensAtdate-timeRequired
ratePoishaintegerRequired
reachableintegerRequired
reviewbooleanRequired
segmentsintegerRequired
textstringRequired
totalSegmentsintegerRequired
Request
curl -X POST "https://app.revisit.cards/v1/developer/sms/campaigns/preview" \
  -H "Authorization: Bearer $DEVELOPER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Your 10th chai is on us this week."
  }'
Response
{
  "data": {
    "alreadyTexted": 0,
    "balance": {
      "credit": 250,
      "pool": 250,
      "total": 12
    },
    "charactersLeft": 92,
    "costPoisha": 40,
    "encoding": "GSM_7BIT",
    "issues": [
      {
        "code": "banned_topic",
        "message": "Your 10th chai is on us this week."
      }
    ],
    "missing": 0,
    "notReachable": 9,
    "opensAt": "2026-11-03T08:41:12.000Z",
    "ratePoisha": 40,
    "reachable": 125,
    "review": false,
    "segments": 1,
    "text": "Collect 10, get 1 free",
    "totalSegments": 120
  },
  "requestId": "7c5b8869-3f29-4cd0-83a6-f3afba744535",
  "success": true
}

List recent SMS campaigns and how each went

GET/sms/campaigns

Scopesms:read

Requires sms:read. Newest first, at most limit (50). Only campaigns of the branches and cards the key may use. counts is per outcome (accepted is handed to the carrier and not reported yet), costPoisha is the accepted segments.

Query

limitinteger
1 to 50. Default 20.
programIduuid
branchIduuid

Returns 200

itemsobject[]Required
items[].bodystringRequired
items[].branchIdstring or nullRequired
items[].canceledReasonstring or nullRequired
items[].costPoishaintegerRequired
items[].countsobjectRequired
items[].counts.acceptedintegerRequired
items[].counts.deliveredintegerRequired
items[].counts.notDeliveredintegerRequired
items[].counts.queuedintegerRequired
items[].counts.rejectedintegerRequired
items[].counts.skippedintegerRequired
items[].counts.unknownintegerRequired
items[].createdAtdate-timeRequired
items[].iduuidRequired
items[].namestringRequired
items[].programIdstring or nullRequired
items[].recipientsintegerRequired
items[].reviewNotestring or nullRequired
items[].segmentsPerMessageintegerRequired
items[].sendAtdate-timeRequired
items[].statusenumRequired
One of pending_review, queued, sending, sent, canceled, rejected.
Request
curl "https://app.revisit.cards/v1/developer/sms/campaigns" \
  -H "Authorization: Bearer $DEVELOPER_API_KEY"
Response
{
  "data": {
    "items": [
      {
        "body": "Your 10th chai is on us this week.",
        "branchId": "5eff65d7-d01c-4bd3-b26a-3d06b21fd005",
        "canceledReason": null,
        "costPoisha": 40,
        "counts": {
          "accepted": 100,
          "delivered": 118,
          "notDelivered": 2,
          "queued": 0,
          "rejected": 1,
          "skipped": 3,
          "unknown": 0
        },
        "createdAt": "2026-11-03T08:41:12.000Z",
        "id": "e01b010d-2c65-48e1-83a0-acad79f8d3b4",
        "name": "Chai card",
        "programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
        "recipients": 125,
        "reviewNote": "The customer used the reward before the refund",
        "segmentsPerMessage": 1,
        "sendAt": "2026-11-03T08:41:12.000Z",
        "status": "pending_review"
      }
    ]
  },
  "requestId": "abd1d179-7aed-46af-887b-178c42f69320",
  "success": true
}

Create an SMS campaign

POST/sms/campaigns

Scopesms:write

Requires sms:write, which no preset grants (an owner ticks it under "Choose permissions"). Runs the app's own create: consent, the plan's campaigns feature, content rules, the segments held up front (403 SMS_CREDIT_SHORT with how many are missing), 422 SMS_CONTENT for unsafe text and 422 SMS_NO_AUDIENCE when nobody can get it. 409 SMS_SUSPENDED while an operator has suspended SMS and 409 SMS_PAUSED while the kill switch is on. Answers 202 with a stable campaignId: the first campaign of a business (and a big one from a new business) waits for an operator (pending_review) and a key cannot approve it; otherwise it is queued and the sender, a bounded job, sends inside 9:00 to 21:00 Dhaka. Every text ends in that customer's own opt-out link. A retried Idempotency-Key never creates a second campaign or holds the balance again; the same key with other content is 409 OPERATION_KEY_CONFLICT. A test key sends nothing real: every message ends skipped with the reason "Test mode: nothing was sent", nothing is charged, and a send the live balance could not cover is refused the same way.

Send an Idempotency-Key header. Safe to retry.

Body

audienceobject
audience.kindenumRequired
One of all, broadcast, segment, individual, location.
audience.segmentenum
One of lapsed_7, lapsed, one_away, reward_ready, new_week, champions.
audience.membershipIdsuuid[]
audience.branchIduuid
bodystringRequired
1 to 1000 characters.
branchIduuid
namestringRequired
Private; customers never see it. 1 to 80 characters.
noWalletOnlyboolean
programIduuid
scheduledFordate-time

Returns 202

campaignIduuidRequired
recipientsintegerRequired
statusenumRequired
One of pending_review, queued, sending, sent, canceled, rejected.

Errors

  • 403SMS_CREDIT_SHORT
  • 422SMS_CONTENT
  • 422SMS_NO_AUDIENCE
  • 409SMS_SUSPENDED
  • 409SMS_PAUSED
  • 409OPERATION_KEY_CONFLICTthe same operation key arrived with different content. The key is still bound to the first request.
Request
curl -X POST "https://app.revisit.cards/v1/developer/sms/campaigns" \
  -H "Authorization: Bearer $DEVELOPER_API_KEY" \
  -H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8ea2e5a3" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Your 10th chai is on us this week.",
    "name": "Chai card"
  }'
Response
{
  "data": {
    "campaignId": "59aed7f3-83e8-43d6-aa83-3d0f98813f1a",
    "recipients": 125,
    "status": "pending_review"
  },
  "requestId": "108155c5-1881-48ff-b5aa-ebee4aaed3b1",
  "success": true
}

Read one SMS campaign and where it stands

GET/sms/campaigns/{id}

Scopesms:read

Requires sms:read. status is pending_review, queued, sending, sent, canceled or rejected. An id of another business, another environment or outside the key's branches and cards is 404.

Path

iduuidRequired

Returns 200

bodystringRequired
branchIdstring or nullRequired
canceledReasonstring or nullRequired
costPoishaintegerRequired
countsobjectRequired
counts.acceptedintegerRequired
counts.deliveredintegerRequired
counts.notDeliveredintegerRequired
counts.queuedintegerRequired
counts.rejectedintegerRequired
counts.skippedintegerRequired
counts.unknownintegerRequired
createdAtdate-timeRequired
iduuidRequired
namestringRequired
programIdstring or nullRequired
recipientsintegerRequired
reviewNotestring or nullRequired
segmentsPerMessageintegerRequired
sendAtdate-timeRequired
statusenumRequired
One of pending_review, queued, sending, sent, canceled, rejected.
Request
curl "https://app.revisit.cards/v1/developer/sms/campaigns/e01b010d-2c65-48e1-83a0-acad79f8d3b4" \
  -H "Authorization: Bearer $DEVELOPER_API_KEY"
Response
{
  "data": {
    "body": "Your 10th chai is on us this week.",
    "branchId": "5eff65d7-d01c-4bd3-b26a-3d06b21fd005",
    "canceledReason": null,
    "costPoisha": 40,
    "counts": {
      "accepted": 100,
      "delivered": 118,
      "notDelivered": 2,
      "queued": 0,
      "rejected": 1,
      "skipped": 3,
      "unknown": 0
    },
    "createdAt": "2026-11-03T08:41:12.000Z",
    "id": "e01b010d-2c65-48e1-83a0-acad79f8d3b4",
    "name": "Chai card",
    "programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
    "recipients": 125,
    "reviewNote": "The customer used the reward before the refund",
    "segmentsPerMessage": 1,
    "sendAt": "2026-11-03T08:41:12.000Z",
    "status": "pending_review"
  },
  "requestId": "34512cb9-26e1-4239-8655-800c8c1bd5a9",
  "success": true
}

Read how each message of a campaign went

GET/sms/campaigns/{id}/messages

Scopesms:read

Requires sms:read. 200 messages a page (pageSize), oldest first. A message names the customer only by customerId, with status and a plain reason for a skip or a refusal (opted out, daily limit, outside sending hours, test mode); never a name or a number.

Path

iduuidRequired

Query

pageinteger
Default 0.

Returns 200

itemsobject[]Required
items[].customerIdstringRequired
items[].idstringRequired
items[].reasonstring or nullRequired
items[].segmentsintegerRequired
items[].sentAtdate-time or nullRequired
items[].statusenumRequired
One of queued, skipped, accepted, unknown, delivered, not_delivered, rejected.
pageSizeintegerRequired
Request
curl "https://app.revisit.cards/v1/developer/sms/campaigns/e01b010d-2c65-48e1-83a0-acad79f8d3b4/messages" \
  -H "Authorization: Bearer $DEVELOPER_API_KEY"
Response
{
  "data": {
    "items": [
      {
        "customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb",
        "id": "d12cc02b-02fe-4a1f-83b0-5414e70fd99c",
        "reason": "Customer returned the order",
        "segments": 1,
        "sentAt": "2026-11-03T08:41:12.000Z",
        "status": "queued"
      }
    ],
    "pageSize": 50
  },
  "requestId": "2a2b282c-b370-4c35-8a7f-35ec851bae34",
  "success": true
}

Cancel an SMS campaign before it goes out

POST/sms/campaigns/{id}/cancel

Scopesms:write

Requires sms:write. Queued messages are removed and every held segment returns. 409 SMS_CAMPAIGN_CLOSED once it finished or was canceled. A retried key replays the first answer and returns nothing twice. Approving, rejecting or reconciling a campaign, the kill switch and suspension stay with operators.

Send an Idempotency-Key header. Safe to retry.

Path

iduuidRequired

Returns 200

bodystringRequired
branchIdstring or nullRequired
canceledReasonstring or nullRequired
costPoishaintegerRequired
countsobjectRequired
counts.acceptedintegerRequired
counts.deliveredintegerRequired
counts.notDeliveredintegerRequired
counts.queuedintegerRequired
counts.rejectedintegerRequired
counts.skippedintegerRequired
counts.unknownintegerRequired
createdAtdate-timeRequired
iduuidRequired
namestringRequired
programIdstring or nullRequired
recipientsintegerRequired
reviewNotestring or nullRequired
segmentsPerMessageintegerRequired
sendAtdate-timeRequired
statusenumRequired
One of pending_review, queued, sending, sent, canceled, rejected.

Errors

  • 409SMS_CAMPAIGN_CLOSED
Request
curl -X POST "https://app.revisit.cards/v1/developer/sms/campaigns/e01b010d-2c65-48e1-83a0-acad79f8d3b4/cancel" \
  -H "Authorization: Bearer $DEVELOPER_API_KEY" \
  -H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8ea58e26"
Response
{
  "data": {
    "body": "Your 10th chai is on us this week.",
    "branchId": "5eff65d7-d01c-4bd3-b26a-3d06b21fd005",
    "canceledReason": null,
    "costPoisha": 40,
    "counts": {
      "accepted": 100,
      "delivered": 118,
      "notDelivered": 2,
      "queued": 0,
      "rejected": 1,
      "skipped": 3,
      "unknown": 0
    },
    "createdAt": "2026-11-03T08:41:12.000Z",
    "id": "e01b010d-2c65-48e1-83a0-acad79f8d3b4",
    "name": "Chai card",
    "programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
    "recipients": 125,
    "reviewNote": "The customer used the reward before the refund",
    "segmentsPerMessage": 1,
    "sendAt": "2026-11-03T08:41:12.000Z",
    "status": "pending_review"
  },
  "requestId": "27994b57-d58b-4d8d-b675-d9d0d1c79211",
  "success": true
}