What are Campaigns?
Campaigns are automated messaging workflows that send personalized messages to leads through Telegram. Each campaign processes leads individually (lead-by-lead), executing a flow of nodes that can send messages, wait for delays, evaluate conditions, and respond to user interactions.Campaign Architecture
Core Components
Campaign Flow (campaign_flows table):
- Defines the visual workflow (nodes and edges)
- Stores campaign metadata (name, description, status, timezone)
- Contains A/B test configuration and variations
- Links to Telegram account for sending messages
flow_executions table):
- One execution per lead in the campaign
- Tracks individual lead progress through the flow
- Stores execution context (variables, node states)
- Maintains status:
pending,running,waiting,completed,responded,failed,paused,cancelled
flow_execution_logs table):
- Audit trail of every node execution
- Records input/output data, status, timestamps
- Used for statistics and debugging
messages table):
- Stores all sent and received messages
- Links to execution and campaign for tracking
- Tracks delivery status and A/B test variation
Processing Model
Campaigns use a worker-based architecture:- UnifiedWorker runs continuously, polling for pending executions
- Each execution is processed independently (lead-by-lead)
- Workers use Redis locks to prevent concurrent processing of the same execution
- Rate limiting is enforced per Telegram account to prevent FloodWaitError
- Response detection runs automatically, checking for replies every 60 seconds
Campaign Statuses
Draft
- Campaign created but not launched
- Can be edited freely
- No executions created yet
Scheduled
- Campaign set to start at a specific date/time
- Timezone-aware: scheduled time is stored in UTC but displayed in campaign’s timezone
- Automatically transitions to
activewhen scheduled time is reached
Active
- Campaign is running and processing executions
- Workers pick up pending executions and process them
- Cannot edit flow configuration (must pause first)
- New leads can be added dynamically
Paused
- Campaign temporarily stopped
- All active executions are paused
- Can be resumed (executions continue from where they stopped)
- Flow configuration can be edited
Completed
- All executions have finished (completed, responded, or failed)
- Campaign automatically transitions to completed when no active executions remain
- Can be reactivated by adding new leads (status changes back to
active)
Cancelled
- Campaign was stopped manually
- All pending executions are cancelled
- Cannot be resumed (must duplicate to restart)
Permissions
Organization Context
Campaigns respect organization isolation: Personal Mode (no organization):- Users can only see/edit campaigns with
organization_id = NULL - Complete isolation from organization data
- Campaigns belong to organization (
organization_idset) - Access controlled by role and
admin_view_enabledsetting
Role-Based Permissions
Owner:- Can view all organization campaigns (if
admin_view_enabled = true) - Can edit any organization campaign (regardless of status)
- Can delete any organization campaign
- Full control over campaign lifecycle
- Can view all organization campaigns (if
admin_view_enabled = true) - Can edit any organization campaign (regardless of status)
- Can delete any organization campaign
- Cannot change organization owner
- Can only view own campaigns (even if
admin_view_enabled = true) - Can edit own campaigns (if status allows)
- Can delete own campaigns
- Cannot view/edit other members’ campaigns
Status-Based Edit Restrictions
Even with proper permissions, editing is restricted by status: Can Edit Flow Configuration:draft- Always editablepaused- Editablescheduled- Editable (before start time)
active- Must pause first (prevents mid-execution changes)completed- Cannot edit (historical record)cancelled- Cannot edit (historical record)
Campaign Processing
Execution Flow
-
Campaign Started:
- Status changes to
active - Executions created for each lead (status:
pending) next_execution_atset tonow()for immediate processing
- Status changes to
-
Worker Picks Up Execution:
- UnifiedWorker queries for executions where
next_execution_at <= now() - Acquires Redis lock to prevent concurrent processing
- Loads campaign flow, contact, and execution context
- UnifiedWorker queries for executions where
-
Node Execution:
- Processes current node based on type:
- START: Initializes execution, moves to first node
- SEND_MESSAGE: Sends message with rate limiting, tag replacement, spintax
- WAIT: Calculates next execution time, sets status to
waiting - CONDITION: Evaluates condition, branches to appropriate path
- END: Marks execution as
completed
- Processes current node based on type:
-
After Node Execution:
- Updates execution context (variables, current_node_id)
- Creates execution log entry (audit trail)
- Calculates next node and
next_execution_at - Releases Redis lock
-
Execution Completion:
- When execution reaches END node or fails
- Status changes to
completed,responded, orfailed - Campaign checks if all executions are done → auto-completes campaign
Rate Limiting
Rate limiting prevents Telegram FloodWaitError: Per-Account Limits:- Cooldown between messages: 30-60 seconds (with 20-40% jitter)
- Daily message limits: Configurable per account profile
- Hourly message limits: Configurable per account profile
- Workers check rate limit before sending
- If cooldown active, execution waits and retries later
next_execution_atadjusted to respect cooldown
conservative: Lower limits, safer for new accountsmoderate: Balanced limitsaggressive: Higher limits (use with caution)
Timezone Handling
Campaigns are timezone-aware: Scheduled Campaigns:scheduled_start_atstored in UTCtimezonefield stores campaign’s timezone (e.g., “America/Sao_Paulo”)- Worker converts UTC to campaign timezone for comparison
- Ensures campaigns start at correct local time
- Duration-based waits are timezone-agnostic (relative)
- Response-based waits use UTC timestamps
Response Detection
Automatic response detection stops sending when lead replies: Detection Process:- UnifiedWorker checks for responses every 60 seconds
- Queries Telegram API for messages after last sent message timestamp
- If response found:
- Execution status →
responded response_detected_attimestamp recordedresponse_textstored (first 500 chars)- Last sent message marked as delivered (response = proof of delivery)
- No further messages sent to this lead
- Execution status →
- Configurable per campaign (
response_timeout_hours) - If no response after timeout, execution continues
- Default: No timeout (waits indefinitely)
- Campaign setting:
pause_on_response - If enabled, execution stops immediately when response detected
- If disabled, execution completes current node before stopping
Flow Builder
Node Types
START Node:- Entry point of campaign
- Defines start type:
immediateorscheduled - For scheduled: requires
scheduled_start_atandtimezone
- Sends text message (with optional media)
- Processes spintax and dynamic tags
- Enforces rate limiting
- Creates message record and execution log
- Duration-based: waits specified time (days/hours/minutes)
- Response-based: waits for lead response with timeout
- Sets
next_execution_atfor future processing
- Evaluates condition based on execution context
- Branches to different paths based on result (
yes/no) - Uses edges with
conditionfield to route flow
- Marks execution as completed
- Campaign checks if all executions done → auto-completes
Flow Configuration
Nodes Array:- Ordered list of all nodes in flow
- Each node has:
id,type,data,position
- Defines connections between nodes
- Sequential edges: connect nodes in order
- Conditional edges: connect condition nodes to branches (
condition: "yes"or"no")
- Nodes processed sequentially by array order
- Edges used only for condition branching
- If no edges, nodes processed in array order
Dynamic Tags
Tag Replacement
Tags are replaced with actual values before sending: Basic Tags:{firstName}→ Contact’s first name{lastName}→ Contact’s last name{username}→ Telegram username (without @){phoneNumber}→ Phone number{groupName}→ Group name (if contact from group)
{cf-fieldName}→ Custom field value- Example:
{cf-companyName}→ “Acme Corp”
- Format:
{tag|fallback} - If tag value missing, uses fallback
- Example:
{firstName|Guest}→ “John” or “Guest”
- Spintax processed first (variations resolved)
- Tags replaced second (with fallbacks)
- Final message sent
Spintax
Spintax allows message variations: Syntax:{option1|option2|option3}- Example:
{Hi|Hello|Hey} {firstName}
- Same lead always gets same variation (deterministic)
- Uses hash of
contact_idas seed - Ensures consistent experience per lead
- Supports nested variations:
{Hi|Hello} {there|here} - Processed recursively until all resolved
- Spintax: 3+ options → random selection
- Tag with fallback: 2 options → data replacement
- Processor distinguishes automatically
A/B Testing
Configuration
Variations:- Multiple flow configurations (A, B, C, etc.)
- Each variation has independent nodes/edges
- Variations can be enabled/disabled independently
- Deterministic: Same lead always gets same variation
- Uses hash of
contact_idfor consistency - Weighted distribution: Configurable weights per variation
- Automatic optimization based on performance metrics
- Compares response rates between variations
- Adjusts weights to favor better-performing variations
- Threshold:
ab_test_optimization_threshold(default: 0.1 = 10%)
- Tracked per variation: messages sent, responses, response rate
- Updated every 5 minutes by worker
- Available via
/campaign-flows/{id}/ab-statisticsendpoint
Statistics
Real-Time Metrics
Execution Metrics:total_contacts: Total leads in campaigncontacts_started: Executions that began processingcontacts_completed: Executions that finished successfullycontacts_failed: Executions that failedcontacts_active: Currently processing executionscontacts_responded: Leads who replied
messages_sent: Total messages sentmessages_failed: Failed send attemptsmessages_delivered: Confirmed deliveriesmessages_not_sent: Pending sends
completion_rate: (completed / total) × 100response_rate: (responded / delivered) × 100delivery_rate: (delivered / sent) × 100
- Statistics computed by database function (
get_campaign_statistics) - Cached for performance (refreshed every 5 minutes)
- Available via
/campaign-flows/{id}/statisticsendpoint
Audit Logs
Execution Logs
Every node execution creates a log entry: Log Fields:node_type: Type of node executedstatus:completed,failed,skippedexecuted_at: Timestamp of executioninput_data: Input context (contact info, message template)output_data: Output result (sent message, contact info)error_message: Error details if failed
sendMessage: Message sent to leadwait: Wait period startedcondition: Condition evaluateddelivery: Delivery status checkedresponse_detection: Response check performed
- Available via
/campaign-flows/{id}/logsendpoint - Filtered by execution for lead-specific view
- Used for debugging and compliance
When Campaigns Stop
Automatic Completion
Campaign automatically stops when:- All executions reach terminal status (
completed,responded,failed,cancelled) - No pending/running/waiting executions remain
- Status changes to
completed
Manual Stopping
Pause:- Temporarily stops processing
- Executions paused (can be resumed)
- Status:
paused
- Immediately cancels all active executions
- Status:
cancelled - Cannot be resumed (must duplicate)
- Toggles
enabledflag tofalse - Cancels all active executions immediately
- Prevents new launches
- Different from pause (cannot launch when disabled)
Response Detection
Ifpause_on_response = true:
- Execution stops immediately when response detected
- Status:
responded - No further messages sent
Fallback Mechanisms
Message Sending Fallback
If primarychat_id fails:
- Tries
telegram_user_id(if available) - Tries
phone_number(if available) - Tries
username(if available) - If all fail, execution marked as
failed
Tag Fallback
If tag value missing:- Uses fallback value if specified:
{tag|fallback} - Otherwise, tag left as-is (prevents broken messages)
Rate Limit Fallback
If rate limit exceeded:- Execution waits for cooldown period
next_execution_atadjusted- Retries automatically on next worker cycle
Best Practices
- Start Small: Test with small audience before full launch
- Monitor Rate Limits: Watch account health and rate limit usage
- Use Fallbacks: Always provide fallback values for tags
- Test Flow: Review flow visually before launching
- Respect Timezones: Set correct timezone for scheduled campaigns
- Monitor Responses: Check response rates and adjust messaging
- Use A/B Testing: Test variations to optimize performance
- Review Logs: Check audit logs for debugging and compliance
