Skip to main contentWhat 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 Execution (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
Execution Logs (flow_execution_logs table):
- Audit trail of every node execution
- Records input/output data, status, timestamps
- Used for statistics and debugging
Messages (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
active when 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
Organization Mode:
- Campaigns belong to organization (
organization_id set)
- Access controlled by role and
admin_view_enabled setting
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
Admin:
- 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
Member:
- 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 editable
paused - Editable
scheduled - Editable (before start time)
Cannot Edit Flow Configuration:
active - Must pause first (prevents mid-execution changes)
completed - Cannot edit (historical record)
cancelled - Cannot edit (historical record)
Note: Basic metadata (name, description, Telegram account) can be updated even when flow is locked.
Campaign Processing
Execution Flow
-
Campaign Started:
- Status changes to
active
- Executions created for each lead (status:
pending)
next_execution_at set to now() for immediate processing
-
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
-
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
-
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, or failed
- 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
Intelligent Cooldown:
- Workers check rate limit before sending
- If cooldown active, execution waits and retries later
next_execution_at adjusted to respect cooldown
Account Profiles:
conservative: Lower limits, safer for new accounts
moderate: Balanced limits
aggressive: Higher limits (use with caution)
Timezone Handling
Campaigns are timezone-aware:
Scheduled Campaigns:
scheduled_start_at stored in UTC
timezone field stores campaign’s timezone (e.g., “America/Sao_Paulo”)
- Worker converts UTC to campaign timezone for comparison
- Ensures campaigns start at correct local time
Wait Nodes:
- 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_at timestamp recorded
response_text stored (first 500 chars)
- Last sent message marked as delivered (response = proof of delivery)
- No further messages sent to this lead
Response Timeout:
- Configurable per campaign (
response_timeout_hours)
- If no response after timeout, execution continues
- Default: No timeout (waits indefinitely)
Pause on Response:
- 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:
immediate or scheduled
- For scheduled: requires
scheduled_start_at and timezone
SEND_MESSAGE Node:
- Sends text message (with optional media)
- Processes spintax and dynamic tags
- Enforces rate limiting
- Creates message record and execution log
WAIT Node:
- Duration-based: waits specified time (days/hours/minutes)
- Response-based: waits for lead response with timeout
- Sets
next_execution_at for future processing
CONDITION Node:
- Evaluates condition based on execution context
- Branches to different paths based on result (
yes/no)
- Uses edges with
condition field to route flow
END Node:
- 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
Edges Array:
- Defines connections between nodes
- Sequential edges: connect nodes in order
- Conditional edges: connect condition nodes to branches (
condition: "yes" or "no")
Processing Logic:
- Nodes processed sequentially by array order
- Edges used only for condition branching
- If no edges, nodes processed in array order
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)
Custom Field Tags:
{cf-fieldName} → Custom field value
- Example:
{cf-companyName} → “Acme Corp”
Fallback Values:
- Format:
{tag|fallback}
- If tag value missing, uses fallback
- Example:
{firstName|Guest} → “John” or “Guest”
Processing Order:
- 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}
Consistency:
- Same lead always gets same variation (deterministic)
- Uses hash of
contact_id as seed
- Ensures consistent experience per lead
Nested Spintax:
- Supports nested variations:
{Hi|Hello} {there|here}
- Processed recursively until all resolved
Distinction from Tags:
- 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
Selection:
- Deterministic: Same lead always gets same variation
- Uses hash of
contact_id for consistency
- Weighted distribution: Configurable weights per variation
Optimization:
- 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%)
Statistics:
- Tracked per variation: messages sent, responses, response rate
- Updated every 5 minutes by worker
- Available via
/campaign-flows/{id}/ab-statistics endpoint
Statistics
Real-Time Metrics
Execution Metrics:
total_contacts: Total leads in campaign
contacts_started: Executions that began processing
contacts_completed: Executions that finished successfully
contacts_failed: Executions that failed
contacts_active: Currently processing executions
contacts_responded: Leads who replied
Message Metrics:
messages_sent: Total messages sent
messages_failed: Failed send attempts
messages_delivered: Confirmed deliveries
messages_not_sent: Pending sends
Rates:
completion_rate: (completed / total) × 100
response_rate: (responded / delivered) × 100
delivery_rate: (delivered / sent) × 100
Calculation:
- Statistics computed by database function (
get_campaign_statistics)
- Cached for performance (refreshed every 5 minutes)
- Available via
/campaign-flows/{id}/statistics endpoint
Audit Logs
Execution Logs
Every node execution creates a log entry:
Log Fields:
node_type: Type of node executed
status: completed, failed, skipped
executed_at: Timestamp of execution
input_data: Input context (contact info, message template)
output_data: Output result (sent message, contact info)
error_message: Error details if failed
Log Types:
sendMessage: Message sent to lead
wait: Wait period started
condition: Condition evaluated
delivery: Delivery status checked
response_detection: Response check performed
Access:
- Available via
/campaign-flows/{id}/logs endpoint
- 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
Stop:
- Immediately cancels all active executions
- Status:
cancelled
- Cannot be resumed (must duplicate)
Disable:
- Toggles
enabled flag to false
- Cancels all active executions immediately
- Prevents new launches
- Different from pause (cannot launch when disabled)
Response Detection
If pause_on_response = true:
- Execution stops immediately when response detected
- Status:
responded
- No further messages sent
Fallback Mechanisms
Message Sending Fallback
If primary chat_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_at adjusted
- 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