Skip to content

Search for a complete word or an exact phrase.

Node.js and Python recipes

These recipes use the standard libraries in Node.js 22.12 or later and Python 3.10 or later. They are readable HTTP examples, not official SDKs. They cover sending, scheduling, batches, message status and every page of a batch.

Download your chosen client and the JSON files into the same private directory:

The supplied numbers illustrate the format only. Before any real request, replace them with recipients you are authorized to contact, review the text and choose a future date. Make these changes before submitting the operation for the first time.

The following commands use Bash. Supply your organization identifier and a persistent private directory outside the repository and any published folder. The key is entered without displaying it and is passed only through the process environment.

Terminal window
export BEFIANA_SMS_BASE_URL='http://127.0.0.1:8000/api/sms/v2/'
export BEFIANA_SMS_ORGANIZATION='replace-with-your-organization-id'
export BEFIANA_SMS_JOURNAL="$HOME/.local/state/befiana-sms/replace-with-your-organization-id"
read -r -s -p 'Organization API key: ' BEFIANA_SMS_API_KEY
export BEFIANA_SMS_API_KEY
printf '\n'

The default URL points to a local test API that you must provide. It does not create a BEFIANA test environment. The client never targets production by default. Once your integration is ready, this change explicitly selects production:

Terminal window
export BEFIANA_SMS_BASE_URL='https://api.befiana.cloud/api/sms/v2/'

BEFIANA_SMS_ORGANIZATION separates your local records; it is not sent to the API. The actual organization is derived from the Bearer key. Check that the key belongs to the stated organization, including after rotation. Use a separate journal for each organization and environment. The clients never store authentication secrets in the journal or print them.

Choose one command for your language. appointment-0001 identifies a stable business operation in your application. Reuse that identifier, file and journal when resuming; do not invent a new identifier for every attempt.

Terminal window
node client.mjs submit appointment-0001 messages immediate.json
python client.py submit appointment-0001 messages immediate.json

These commands really create an SMS when targeting an active API with a valid key. They require sms.send and may consume credits. An accepted response does not prove delivery. The displayed result contains the identifier and status, without the phone number or SMS text.

For scheduling, prepare scheduled.json with send_at and use another deliberate operation:

Terminal window
node client.mjs submit appointment-0002 messages scheduled.json
python client.py submit appointment-0002 messages scheduled.json

For a batch, prepare distinct recipients and the date in batch.json:

Terminal window
node client.mjs submit event-0001 message-batches batch.json
python client.py submit event-0001 message-batches batch.json

Schedules and batches require sms.schedule. Acceptance reserves credits and processing may send real SMS messages. See the scheduling, batch, segment and credit guides to prepare requests and calculate the required credits. These clients provide no cancellation operation.

Before the first network call, the client durably saves the operation, its declared organization, URL, resource, UUID Idempotency-Key and exact JSON body. Any later change to the body or scope is rejected locally.

Scroll horizontally if needed. Keyboard: Tab, then the left and right arrow keys.

Local stateBehavior
pendingAt most three attempts per invocation, always with the same key and body.
completeReturn the saved result; no new POST. Use status for an updated state.
follow_upA response supplied message_id. Resuming performs a status GET only.
review_requiredStop POST requests. Review the error and business records before deciding what to do.

A lost response remains pending, even if the server accepted the SMS. Running the command again with its journal allows an idempotent replay. Network failures, 429, 500, 503 without an identifier and 409 idempotency_in_progress have bounded retries. Delays increase between attempts; Retry-After is respected. If the requested delay exceeds 60 seconds, the command stops instead of retrying too early; wait before a manual resumption.

A 410 personal_data_erased is recorded as review_required and stops POST requests. Retain the operation and its key in the journal; do not reset that tracking or resubmit the message with a new key. See the erasure case.

A provider_result_unknown response with message_id is saved before retrieving status. Even if that read fails, the next invocation only reads the message. Any other error with message_id, including dispatch_unavailable, follows this conservative rule. Without an identifier, an uncertain provider outcome or a 502/504 error stops POST requests and requires manual review. Never interpret unknown as “not sent”.

Replace the example UUID with the one returned for your message. These GET commands require delivery.read and create no SMS:

Terminal window
node client.mjs status 00000000-0000-4000-8000-000000000001
python client.py status 00000000-0000-4000-8000-000000000001

For a batch, use its UUID:

Terminal window
node client.mjs batch-messages 00000000-0000-4000-8000-000000000001
python client.py batch-messages 00000000-0000-4000-8000-000000000001

The client reads every page until next_cursor: null, encodes the cursor as the cursor parameter value, then displays identifiers and statuses. It explicitly stops on an HTTP error, invalid page, repeated cursor or more than 1,000 pages; it never presents a partial list as complete. Results can change during pagination: this is not a transactional snapshot of deliveries.

The JSON file for each operation contains the recipient and text needed for an exact replay: protect this personal content, backups and directory access. The clients create private files on compatible systems; on Windows, also configure the directory ACLs. Never put an API key in message text. Error output contains the HTTP status and a stable code, without copying error bodies or secrets.

An exclusive lock prevents two processes sharing this directory from submitting the same operation concurrently. An abrupt exit or any failed journal write leaves the .lock file in place. The JSON may still say pending even though the API already accepted the SMS or reported an uncertain result. Stopped processes alone do not justify removing the lock. Before manual recovery, confirm they have stopped, investigate the actual outcome and the private journal’s consistency, then durably record the correct decision. Read status if you know the identifier; otherwise, ask support to help clarify the operation. While the outcome remains undetermined, retain the lock, original key and body without another POST. No timeout automatically removes the lock. A corrupt JSON file stops the client; it never silently generates a new key.

Writes use a temporary file, fsync, then atomic replacement; the directory is also synchronized where the OS supports it. This recipe assumes a persistent local disk and a single host, with storage that honors those operations. It does not guarantee survival of every hardware failure, handle network disks or replace a transactional database for multiple servers. Losing the journal loses the local protection against creating another intent. Never delete or reset an operation to bypass an error.

Node limits each request to 30 seconds; Python configures a 30-second timeout on blocking network operations without guaranteeing total duration against a very slow stream. Redirects are refused and GET requests do not run in a monitoring loop. Methods accept an injected transport for your tests; repository recipes are verified offline without contacting production. Adapt them to your storage, permissions and business records before automating them.