Skip to main content

SMS (prepaid)

SMS on SendSeven is prepaid. Every SMS has two parts, and they are paid in two different ways:

PartWhat it isHow it is paid
Provider feeThe carrier network cost for the destination country, charged per segmentDrawn from the workspace's prepaid SMS balance when the SMS is sent
Your feeThe SendSeven message rate of your plan, charged once per message, however many segments it hasCounts against your plan's included message pool, then billed on your monthly invoice like any other message

So an SMS needs credit on the SMS balance to go out at all, and it also counts as a SendSeven message on your plan. Inbound SMS (messages your customers send you) is free and never touches the balance.

Who can use SMS

SMS is available on paid accounts after SMS access has been requested and approved in the app (Settings > Connected Channels > SMS). Trial accounts can't send SMS.

The SMS balance​

One balance per workspace​

The SMS balance belongs to the workspace that owns the SMS channel. Every workspace with SMS has its own balance; workspaces on the same billing account do not share it. (This is different from the RCS wallet, which belongs to the billing account.)

All SMS channels (senders) of a workspace draw from the same balance.

There is no minimum balance​

You can spend the balance down to €0.00. A send is blocked only when the balance is used up (see When the balance reaches €0). SendSeven does not keep a safety margin on top.

Top-ups​

You add credit with a top-up in the app, on the SMS channel page (Settings > Connected Channels > SMS). Top-ups are not available through the public API.

RuleValue
First top-up (during SMS setup)At least €20 net
Later manual top-upsAt least €5 net
PaymentCard only (SEPA Direct Debit is not used for SMS top-ups)
VATAdded on top where it applies; each top-up gets its own paid invoice
Credit addedThe net amount

Credit usually appears within seconds. Sometimes part of it arrives a few minutes later. Until then, pending_transfer_cents on GET /sms/balance shows the amount still on its way. You are charged once, and the full amount always arrives.

The fee for a dedicated phone number (monthly or yearly) is charged separately to your card. It is never taken from the SMS balance.

Auto top-up​

Auto top-up charges a saved card automatically when the balance falls below a threshold you choose.

SettingRule
Amount per top-upBetween auto_topup_min_amount_cents and auto_topup_max_amount_cents net (currently €20 to €100), returned by GET /sms/balance
ThresholdLower than the amount
PaymentA saved card; each automatic top-up is a normal top-up with its own invoice
LimitsAt most three automatic attempts per day, a few minutes apart, and none while earlier credit is still on its way

If the card is declined or needs a 3D Secure confirmation that can't be completed without you, nothing is charged, auto top-up is switched off, and the workspace owners and admins get an email and an in-app notification. Fix the card and switch auto top-up on again.

Changing auto top-up charges a card without anyone present, so it needs the billing:update permission (the Owner and Billing roles). Others see the settings read-only; auto_topup_can_edit on GET /sms/balance tells you which case applies to the caller.

Low-balance warnings​

When the balance drops below the warning threshold (€2.00 by default), the workspace owners and admins get an email and an in-app notification, at most once a day. Set your own threshold with PUT /sms/low-balance-threshold.

What an SMS costs​

Segments​

An SMS is sent in segments. A long message is split into several segments, and the carrier joins them on the phone. The provider fee is charged per segment, so a two-segment message costs twice the provider fee of a one-segment message. Your fee is charged once per message, whatever its length.

cost of one SMS = provider fee × segments + your fee

For example, a three-segment SMS with a provider fee of €0.07 and a fee of €0.03 costs €0.21 from the SMS balance plus one message (€0.03) on your plan: €0.24 in total, not €0.30.

How many characters fit in a segment depends on the encoding, which SendSeven picks automatically from the text:

EncodingWhenOne segmentEach segment of a longer message
GSM-7Every character is in the GSM-7 alphabet (Latin letters, digits, common punctuation, some accented letters such as ä ö ü é)up to 160 characters153 characters
Unicode (UCS-2)At least one character is outside GSM-7, for example an emoji, ł, ş, Cyrillic or Chineseup to 70 characters67 characters
  • These GSM-7 characters count as two: € [ ] { } \ ^ ~ | and the form feed.
  • One emoji or other non-GSM-7 character switches the whole message to Unicode, which more than halves the room per segment.
  • An SMS campaign with the opt-out link enabled appends a short unsubscribe link to every message. It counts towards the length.

Destination country​

The provider fee depends on the destination country (the recipient's number). Your fee is the per-message rate of your plan. Both are shown in the app before you send: on the SMS card in Settings > Connected Channels, in the segment counter while you write, and in the campaign cost estimate. For the SendSeven plan prices, see sendseven.com/pricing.

To get the numbers in code, use GET /sms/message-pricing for one recipient and text, or GET /sms/pricelist for every country.

The plan's message pool​

Every outbound SMS that was sent or delivered counts as one SendSeven message on your plan. It uses your plan's included message pool first (the pool is shared by all workspaces on the billing account), and anything beyond the pool is billed at your plan's message price on the monthly invoice. Some plans have a separate SMS message price; the invoice then shows an SMS messages line instead of counting SMS under Messages.

The pool covers only your SendSeven fee. The provider fee is always paid from the SMS balance, also while the pool still has room.

Not billed: inbound SMS, and SMS that ended as failed (including SMS blocked for a used-up balance).

The month's usage across channels, including SMS, is described in the Billing overview; per-workspace numbers are in Multi-workspace usage.

When the balance reaches €0​

Single sends (POST /messages)​

The send request itself does not fail. POST /messages accepts the SMS (201) and sending happens a moment later. When the balance is used up at that point, the message ends as failed instead of being sent:

{
"id": "0b7c3e9a-4f1d-4a8e-9c2b-5d6e7f8a9b0c",
"platform": "sms",
"direction": "outbound",
"status": "failed",
"error_code": "insufficient_sms_balance",
"error_message": "[ERR:sms_insufficient_balance:0.00:0] SMS send blocked: balance 0.00 EUR is used up (floor 0 EUR). Top up your SMS credit to resume sending."
}
  • Read the outcome from the message (GET /api/v1/messages/{message_id}): status is failed and error_code is insufficient_sms_balance. Use error_code in code. The bracketed prefix of error_message is meant for display and may change.
  • The same result appears when the balance runs out between the check and the send and the carrier side refuses for missing credit.
  • The message is not retried and not sent automatically after a top-up. Send it again once there is credit.
  • A blocked SMS is never billed.
  • The message.failed webhook fires for it, once.
  • The workspace owners and admins get a "sends blocked" email and in-app notification (at most once a day).
  • Receiving SMS keeps working at €0.

Other SMS send failures you can see in error_code:

error_codeMeaning
insufficient_sms_balanceThe SMS balance is used up
sms_not_provisionedThe SMS setup of the workspace is not finished
sms_duplicate_sendThe same text went to this recipient within the last 3 minutes
sms_daily_limit_reachedThe daily limit for this recipient is reached; try again tomorrow

The message.failed webhook​

A message that fails while it is being sent, a blocked SMS included, triggers message.failed, with the same payload as a failure the carrier reports later. The reason is in data.error, and data.message.meta.last_failed_error.errors[] holds the structured error. The code there is the same value as the message's error_code:

{
"type": "message.failed",
"data": {
"message": {
"id": "0b7c3e9a-4f1d-4a8e-9c2b-5d6e7f8a9b0c",
"platform": "sms",
"direction": "outbound",
"status": "failed",
"error_code": "insufficient_sms_balance",
"failed_at": "2026-10-23T09:15:02Z",
"meta": {
"last_failed_error": {
"captured_at": "2026-10-23T09:15:02Z",
"errors": [
{
"code": "insufficient_sms_balance",
"message": "[ERR:sms_insufficient_balance:0.00:0] SMS send blocked: balance 0.00 EUR is used up (floor 0 EUR). Top up your SMS credit to resume sending."
}
]
}
}
},
"error": "[ERR:sms_insufficient_balance:0.00:0] SMS send blocked: balance 0.00 EUR is used up (floor 0 EUR). Top up your SMS credit to resume sending."
}
}

The example is shortened; the full event also carries conversation, contact and contact_method like every message event (see Webhook events). Each failed message triggers message.failed once: a send that fails and then succeeds on an automatic retry triggers none, and a duplicate delivery inside SendSeven does not trigger a second one. last_failed_error is also stored on the message, so GET /messages/{message_id} shows it.

Contact verification codes (POST /verifications)​

A verification code sent by SMS is checked right away. With no credit, POST /verifications answers 402:

{
"detail": {
"error": "...",
"code": "insufficient_sms_balance",
"error_code": "insufficient_sms_balance",
"message": "..."
}
}

See Contact Verification.

Campaigns​

When an SMS campaign hits a used-up balance, SendSeven pauses the campaign instead of failing every remaining recipient:

  1. The recipient(s) whose send hit the empty balance end as failed with error_code insufficient_sms_balance, and the campaign.message.failed webhook fires for them. (A campaign send that fails never becomes a conversation message, so campaigns report failures with campaign.message.failed, not message.failed.)
  2. The campaign moves to status paused with throttle_reason insufficient_sms_balance.
  3. Every recipient that was not sent yet stays pending. Nobody who already got the message is affected.
  4. The campaign creator gets an in-app notification, and the owners and admins get the low-balance alert.

The campaign does not continue on its own after a top-up. To finish it:

  1. Top up the SMS balance.

  2. Re-arm the failed recipients:

    curl -X POST "https://api.sendseven.com/api/v1/campaigns/{campaign_id}/retry-failed" \
    -H "Authorization: Bearer s7_api_your_token" \
    -H "Content-Type: application/json" \
    -d '{"error_codes": ["insufficient_sms_balance"]}'
    {"success": true, "id": "c3d4e5f6-7a8b-49c0-d1e2-f3a4b5c6d7e8", "requeued": 3, "paused": true}

    On a paused campaign this does not resume sending. It only puts the failed recipients back in line ("paused": true).

  3. Resume the campaign with POST /api/v1/campaigns/{campaign_id}/resume. The re-armed recipients and everyone still pending are sent.

Both calls need the campaigns:send scope. See Messaging Campaigns for retry-failed in detail (cool-down, other error codes).

Check the balance before a large send

Compare what the campaign takes from the SMS balance (provider fee × segments × recipients; your fee is not taken from the balance) with balance_eur before you send, or switch on auto top-up. A campaign paused for balance needs two calls to finish, and recipients wait until then.

Required scopes​

ActionEndpointScope
Read the balance and its settingsGET /sms/balancechannels:read
Set the low-balance warningPUT /sms/low-balance-thresholdchannels:update
Estimate the price of one SMSGET /sms/message-pricingchannels:read
Get the per-country price listGET /sms/pricelistchannels:read

Endpoints​

All paths are relative to https://api.sendseven.com/api/v1. EUR amounts are returned as decimal strings.

Get the balance​

GET /sms/balance
Query parameterDescription
force_refreshtrue reads the balance live instead of the value cached for up to 5 minutes. Default false
{
"balance_eur": "12.40",
"auto_topup_enabled": true,
"auto_topup_threshold_cents": 1000,
"auto_topup_amount_cents": 5000,
"last_checked_at": "2026-10-23T09:12:44Z",
"low_balance_warn_eur": "2.00",
"low_balance_warn_is_default": true,
"pending_transfer_cents": 0,
"auto_topup_available": true,
"auto_topup_has_payment_method": true,
"auto_topup_last_attempt": {
"status": "succeeded",
"amount_cents": 5000,
"created_at": "2026-10-20T07:03:10Z",
"failure_code": null
},
"auto_topup_min_amount_cents": 2000,
"auto_topup_max_amount_cents": 10000,
"auto_topup_can_edit": false
}
FieldDescription
balance_eurCurrent SMS balance of the workspace. "0.00" when SMS is not set up yet
auto_topup_enabled, auto_topup_threshold_cents, auto_topup_amount_centsAuto top-up settings (net, cents)
low_balance_warn_eurThe warning threshold in effect; low_balance_warn_is_default is true when it is the default
pending_transfer_centsPaid top-up credit still on its way to the balance (net, cents)
auto_topup_availablefalse while auto top-up can't be switched on
auto_topup_has_payment_methodA card for auto top-up is saved
auto_topup_last_attemptThe latest automatic top-up (status pending, succeeded or failed; failure_code for a declined card) or null
auto_topup_can_editThe caller may change auto top-up (billing:update)

Set the low-balance warning​

PUT /sms/low-balance-threshold
{"threshold_eur": 5.00}

threshold_eur is between 0 and 1000 EUR with at most 2 decimals. null goes back to the default. The response is the same object as GET /sms/balance.

Estimate the price of one SMS​

GET /sms/message-pricing?phone=%2B4915112345678&text=Your%20order%20has%20shipped%20...
Query parameterDescription
phoneRecipient number in E.164. Either phone or country is required
countryISO 3166 alpha-2 code, for example DE
textOptional. With a text, the response adds encoding and segment count, and the totals cover all segments. Without a text, the totals are for a one-segment SMS
append_opt_out_linktrue prices the text as a campaign SMS with the opt-out link appended
{
"country_iso": "DE",
"country_name": "Germany",
"prefix": "49",
"platform_fee_eur": "0.0650",
"sendseven_fee_eur": "0.0200",
"total_per_segment_eur": "0.0850",
"price_per_message_eur": "0.0850",
"encoding": "GSM-7",
"char_count": 210,
"segments": 2,
"carrier_fee_total_eur": "0.1300",
"sendseven_fee_total_eur": "0.0200",
"total_eur": "0.1500",
"is_supported": true,
"quote_required": false,
"source": "cache"
}

The amounts above are an example, not current prices.

FieldDescription
platform_fee_eurProvider fee per segment, taken from the SMS balance. The lowest across the country's networks, so a specific number can cost a little more
sendseven_fee_eurYour plan's message rate, charged once per message. It counts against the plan's message pool
carrier_fee_total_eurplatform_fee_eur × segments: what this SMS takes from the SMS balance
sendseven_fee_total_eurYour fee for this SMS. The same as sendseven_fee_eur, whatever the segment count
total_eurcarrier_fee_total_eur + sendseven_fee_total_eur: the estimated price of this SMS
total_per_segment_eur, price_per_message_eurOlder fields, kept for compatibility. Both are platform_fee_eur + sendseven_fee_eur, the price of a one-segment SMS. Don't multiply them by segments; use total_eur
is_supportedfalse for a country SendSeven does not send SMS to (quote_required is then true)

Get the price list​

GET /sms/pricelist

Returns countries[] (iso, name, prefix, platform_fee_eur, sendseven_fee_eur, total_per_segment_eur, is_supported), plus currency, supported_countries and last_updated. As above, platform_fee_eur is per segment, sendseven_fee_eur is per message, and total_per_segment_eur is the price of a one-segment SMS. A message with n segments costs platform_fee_eur × n + sendseven_fee_eur.

Next steps​