oqimDocs
API referenceConsole

Start here

  • Introduction
  • Getting started

Guides

  • Telegram accounts
  • Recipients
  • Campaigns
  • Scheduling
  • Webhooks
  • AI writing assistant

AI Seller

  • AI Seller
  • Channels
  • Live inbox
  • Past conversations and privacy
  • Business insights

API

  • Authentication
  • API reference
  • SDK
  • Errors
  • Rate limits

Trust and operations

  • Security
  • Compliance
  • Administration
Docs / Campaigns

Start here

  • Introduction
  • Getting started

Guides

  • Telegram accounts
  • Recipients
  • Campaigns
  • Scheduling
  • Webhooks
  • AI writing assistant

AI Seller

  • AI Seller
  • Channels
  • Live inbox
  • Past conversations and privacy
  • Business insights

API

  • Authentication
  • API reference
  • SDK
  • Errors
  • Rate limits

Trust and operations

  • Security
  • Compliance
  • Administration

Campaigns

A campaign pairs one message with an audience, the accounts that send it, and the hours it may go out. This page covers its lifecycle, the checks before launch, how to write the message, and what Oqim guarantees about delivery.

Lifecycle#

DRAFT→VALIDATING→SCHEDULED→RUNNING⇄PAUSED→COMPLETED
From draft, scheduled, running or paused→CANCELLED

Scheduled campaigns can be paused too; resuming one that never started returns it to scheduled.

StatusMeaningWhat can change
DRAFTBeing written. Nothing is sent. The only status that can be deleted.Everything
VALIDATINGLaunch passed its checks and is freezing the message and the audience. Lasts only as long as the launch request.Nothing
SCHEDULEDLaunched and waiting for its start time or window. The message is frozen.Name, description, end time, window, days, accounts
RUNNINGSending.Name, description
PAUSEDStopped by a person or by Oqim; status_reason says which and why.Same as scheduled
COMPLETEDEvery recipient reached a final state, or the end time passed; recipients not yet sent to by then are marked cancelled.Nothing
CANCELLEDStopped for good. Recipients not yet sent to are marked cancelled.Nothing
FAILEDAn error Oqim couldn't recover from; status_reason explains.Nothing
  • Only drafts can be launched (POST /campaigns/{id}/start). Launching freezes the message and the audience: editing a template or a list afterwards doesn't change what's sent or to whom.
  • Resuming returns a campaign to RUNNING if it had started sending, otherwise to SCHEDULED. It's refused when the end time has passed (move end_at first) or none of its accounts can send.
  • Scheduled, running and paused campaigns count toward your plan's limit on active campaigns.
  • Duplicate copies any campaign into a new draft with the same message, audience, accounts and schedule, including a repeat rule.
  • A campaign with a repeat rule runs as a series: each run is a campaign of its own going through this lifecycle, and Oqim adds and launches the next run when one completes. Cancelling a run stops the series. See Repeating campaigns.

When Oqim pauses a campaign

  • An account in it stopped: Telegram restricted it, its session ended, it couldn't reach Telegram, or it hit a flood wait longer than the platform allows (15 minutes by default).
  • Its failed and unreachable sends passed 35% of attempts, judged once it has made 40 attempts (platform defaults). This also opens an abuse report for the platform's administrators, because a high failure rate usually means the audience didn't expect the message. After you resume, only the sends made since the pause are judged.
  • The organization was suspended; once it's reactivated, you resume these campaigns yourself. Or a platform administrator paused this campaign, which only the platform's support can undo.

Fix the cause, then resume. Paused campaigns never resume on their own.

Pre-launch validation#

Launch runs sixteen checks. A failed check blocks launch; a warning doesn't. Run them any time with POST /campaigns/{id}/validate; the report is also saved on the campaign as validation. If launch finds a failure it answers 422 campaign_invalid with the report in the response's validation field; see Errors.

CheckFails whenWarns when
Organization activeorganization_activeThe organization is suspended.—
Permission to launchpermissionYour role lacks campaigns:execute.—
Acceptable-use policypolicy_acceptedThe current policy version isn't accepted.—
Accounts selectedaccounts_selectedNo account is selected.—
Accounts authorizedaccounts_authorizedA selected account needs sign-in, is restricted, disconnected or in error, or belongs to another organization.—
Accounts healthyaccounts_healthyNone of the selected accounts is active.Some are paused or cooling down, so sending will be slower.
Recipientsrecipients_validThe audience is empty, or nobody in it can receive messages.—
Opt-outs excludedopt_outs_excludedNever fails.Never warns; reports how many opted-out and suppressed people will be skipped.
Unknown consentunknown_consent—People with unknown consent will be skipped (or included, if the platform allows it).
Messagemessage_validThere's no text and no attachment, or the text is over 4,096 characters (1,024 with an attachment) once variables are filled in.—
Variablesvariables—Some recipients have no value for a variable that has no fallback.
Opt-out linkopt_out_link{{opt_out_url}} is missing and the platform requires it.{{opt_out_url}} is missing.
Scheduleschedule_validUnknown time zone, empty window, no days, an end before the start or in the past, a window that never opens before the end, or a repeating campaign without a start time.—
Plan quotaquotaYour plan's limit on active campaigns is reached.—
Recipient limitmax_recipientsThe sendable audience is over the per-campaign maximum (50,000 by default).—
Delivery capacitycapacity—Delivery probably won't finish before the end time, or no active account can send in the window.

Message length and variables are checked by rendering the message for up to 5,000 recipients from the audience. The report's estimated_hours is deliberately conservative: each active account sends at most one message per interval and its daily limit per open day, only while the window is open, plus 15% for retries and flood waits.

Writing messages#

parse_mode decides how the text is read. MARKDOWN, the default, uses the syntax below. HTML accepts Telegram's subset (b, strong, i, em, u, ins, s, strike, del, code, pre, blockquote, tg-spoiler and a href) and shows any other tag as text. PLAIN sends the text exactly as written.

You writeRecipients see
**bold**bold
__italic__ or _italic_italic
~~strike~~strike
||spoiler||spoiler
`code`code
```pre```preformatted block
[label](https://url)label
> quoted linequoted line
  • Links may use http, https or tg://. Consecutive lines starting with > form one quote.
  • Formatting doesn't span lines, except code blocks.
  • Text is escaped before formatting is applied, and variable values are escaped too, so recipient data can't inject formatting. A recipient named <b>Ann</b> sees exactly that.
Message
Hi {{first_name|there}},

**Tashkent Expo** opens tomorrow at 10:00 in {{location}}.
Your badge number: `{{badge}}`

> Doors close at 18:00.

[Floor plan](https://expo.example/plan)
Stop these messages: {{opt_out_url}}

Variables#

VariableValue
{{first_name}}First name
{{last_name}}Last name
{{full_name}}First and last name, separated by a space
{{username}}Username with @, e.g. @aziza_k
{{telegram_id}}Numeric Telegram ID
{{opt_out_url}}The recipient's personal opt-out link; see Opt-out
{{event_date}}Any attribute, by its key. {{attributes.event_date}} works too.

Add a fallback after a pipe: {{first_name|there}} renders there when the recipient has no first name. A variable with no value and no fallback renders as nothing, and validation warns about it. Names may contain letters, digits, underscores and dots, and spaces inside the braces are ignored.

Length limits#

Telegram allows 4,096 characters in a text message and 1,024 in the caption of a photo, document or video. Oqim counts the visible text after variables are filled in; formatting markup doesn't count. Because names and attributes vary, validation checks the longest rendering across your audience, not just the preview.

Attachments#

A campaign can carry one attachment, uploaded with POST /attachments or in the composer. JPEG, PNG and WebP images go out as photos (up to 10 MB), videos as videos, and anything else as a document; files can be up to 20 MB. The message text becomes the caption. disable_link_preview turns off the preview card Telegram would otherwise show for the first link.

Delivery and idempotency#

Each recipient in a campaign has exactly one delivery record. The worker that sends it gives Telegram a random_id derived from that record. If a send is retried after a timeout, a restart or a network error, Telegram recognises the repeat (RANDOM_ID_DUPLICATE) and the recipient still gets one message.

Send jobs carry only IDs and reload everything from the database right before sending, so a delayed or duplicated job can't send an outdated message or reach someone who opted out after launch. Network errors, timeouts and Telegram-side errors are retried on the same account with growing delays, up to 8 times; after that the delivery fails. Five such errors in a row mark the account disconnected.

Delivery statusMeaning
PENDING, QUEUED, SENDINGNot sent yet, waiting for a worker, or in flight.
DELIVEREDTelegram accepted the message.
RECIPIENT_UNAVAILABLEThe recipient can't be reached from this account: privacy settings, they blocked it, the username doesn't exist or changed hands, the account was deleted, the ID can't be resolved, or it's a bot.
FAILEDTelegram refused the message, for example as too long; last_error has the reason.
SKIPPEDNot sent on purpose, for example because the person opted out after launch.
CANCELLEDThe campaign was cancelled, or reached its end time, before this recipient's turn.

Note

Follow deliveries as they happen with the message.delivered and message.failed webhook events, or poll GET /campaigns/{id}/statistics for totals, a lane per account and an ETA.
PreviousRecipientsNext Scheduling