Campaign Sender
Checking session...
Checking session...
Enter the dashboard API token to continue.
Live campaign operations and system health.
Live health, sending progress, and attention items across WAHA, Supabase, Postgres, and the Docker worker.
Postgres, Supabase, WAHA, worker, and webhook health.
Create, save, preview, and keep editing drafts before approval.
Run internal test lists, monitor worker-based tests, and download reports before real sending.
Draft campaigns stay editable after test runs. Existing test logs keep their old snapshot.
| Draft | Message | Latest test | Next step | Actions |
|---|---|---|---|---|
| Drafts ready for testing appear here. | ||||
These are worker-controlled internal sends using real rate limits.
Search test runs and download CSV/JSON reports.
| Created | Campaign | List | Status | Progress | Actions |
|---|---|---|---|---|---|
| Test history appears here. | |||||
Select a draft or test run to start, pause, resume, cancel, and inspect recipient logs.
Live operations: approved, running, paused, queued, and active test campaigns.
| Campaign | Status / Next action | Real progress | Latest test | Worker reason | Next attempt | Controls |
|---|
Search and manage every non-archived campaign.
| Name | Status | Real Progress | Latest Test | Last Worker Event | Next Attempt | Actions |
|---|
Durable worker decisions from Postgres, without opening Docker logs.
| Time | Event | Campaign | Queue | Status | Reason | Message | Details |
|---|---|---|---|---|---|---|---|
| Worker logs will appear here. | |||||||
Preview live Supabase users first. Import writes only to campaign Postgres.
| Name | Phone | City / Source | Brands | Validity | Sync State | CRM Updated | ||
|---|---|---|---|---|---|---|---|---|
| Choose a brand and load the live audience. | ||||||||
Imported contacts available for queue building.
| Name | Raw phone | WhatsApp chat id | City / Source | Brands | Validity | Suppressed | Last imported | CRM updated |
|---|---|---|---|---|---|---|---|---|
| Load contacts or import an audience first. | ||||||||
Upload reusable campaign files. Remote WAHA sends by public URL when configured, otherwise the worker uses base64 fallback.
Select an asset in campaign composer or copy its URL for advanced JSON templates.
Internal recipients only. Use full country code, for example +917972495890.
Build reusable groups of active test users. Use 10-50 users for realistic checks.
Campaigns use these lists for worker-based test runs.
| Name | Members | Status | Description | Actions |
|---|---|---|---|---|
| No test lists yet. | ||||
Active users can be added to test lists. These users never affect campaign audiences.
| Name | Raw phone | WhatsApp chat id | Status | Notes | Actions |
|---|---|---|---|---|---|
| No test users yet. | |||||
Defaults for new campaigns and the Docker worker. Start conservative for new WhatsApp sessions.
Use this URL in WAHA so delivered/read/reply events update the dashboard.
No setting can guarantee no ban. This estimates pacing only.
| Session stage | Daily limit | Hourly limit | Delay | Notes |
|---|---|---|---|---|
| New/cold | 30/day | 10-15/hour | 60-180 seconds | Use for days 1-2 or unknown account health. |
| Warming | 60/day | 15-20/hour | 60-150 seconds | Use after low failures and normal replies. |
| Warm | 100/day | 20-25/hour | 45-120 seconds | For tested sessions only. Raising above this is high risk. |
Persistent audit trail for auth and dashboard mutations. These logs cannot be cleared from the dashboard.
| Time | Actor | Action | Entity | Campaign | Summary | Details |
|---|---|---|---|---|---|---|
| Admin activity will appear here. | ||||||
This dashboard is the separate WhatsApp campaign sender for the CRM. It reads live audience data from Supabase, imports selected contacts into campaign Postgres, lets admins build and test campaigns, and uses the Docker Python worker to send slowly through WAHA.
The old n8n sending path is deprecated. Campaign sending, testing, reporting, settings, media, logs, and admin activity are now managed from this dashboard plus the campaign_worker service.
The project is isolated from the CRM application. It reads CRM data through Supabase credentials, writes only to the campaign Postgres database, and never changes the existing CRM API or frontend.
4100. Handles token login, campaign controls, audience import, media upload, settings, WAHA webhooks, notifications, and monitoring.| Page | Purpose | Main actions |
|---|---|---|
| Overview | Executive health view for contacts, campaigns, WAHA, worker, notifications, and recent activity. | Refresh status, open attention items, monitor system health. |
| Campaign Builder | Persistent workspace for creating and editing campaign drafts. | Save Draft, Save Changes, One-Time Preview, Save & Open Testing, New Draft. |
| Campaign Testing | Dedicated area for worker-based test runs and test history. | Start Test Run, pause/resume/cancel, inspect recipient logs, download reports. |
| Running Campaigns | Focused view for campaigns that are actively sending, paused, or waiting. | Start, pause, cancel/archive, inspect progress and latest worker reason. |
| All Campaigns | Campaign management list for drafts, approved campaigns, completed campaigns, and archived items. | Edit, duplicate, approve, build queue, archive, delete empty drafts. |
| Worker Logs | Durable worker event feed from Postgres. | Filter sent/failed/paused/error events, open event payload details. |
| Audience | Live Supabase preview and compare screen. | Load brand audience, search/filter, import selected, import all matching. |
| Contacts | Campaign database contacts available for queue building. | View totals, brand counts, validity, suppression, pagination, and search. |
| Media Gallery | Upload and reuse image/audio/video/file assets. | Upload, preview, copy URL, use asset in campaigns, delete unused media. |
| Test Users | Internal recipients and reusable test lists. | Create/edit/delete users, activate/deactivate, create test lists and members. |
| Settings | Global defaults used by new campaigns and the worker. | Edit limits, timezone, windows, delays, attempts, and poll interval. |
| Admin Activity | Persistent audit log of login and mutation actions. | Search/filter audit history and inspect metadata. Logs cannot be cleared. |
| Docs | This manual. | Read operator flow, settings, templates, troubleshooting, and developer notes. |
vaseline, and load the live Supabase preview.Audience reads Supabase live. Contacts reads campaign Postgres only. Previewing does not write anything; importing writes or updates local campaign contacts.
user_brand_registrations.brand. Fallback is users.brand only when no registration row exists. Banned CRM users are excluded.registered_brands keeps the full list.+243 rules and become WAHA chat ids like 243XXXXXXXXX@c.us. Ambiguous numbers are invalid and excluded from real queue building.Campaign Builder is the draft workspace. Saving a draft keeps the editor loaded so you can continue testing and editing instead of losing your place.
{{brand}} Campaign Draft.vaseline, lifebuoy, and pepsodent. Queue building filters contacts by this brand./api/sessions. Use a WORKING session for real campaigns. Refresh reloads changed session names.simple uses friendly fields. advanced uses allowlisted JSON for supported message types.Editing rule: DRAFT campaigns remain fully editable after One-Time Previews and worker Test Runs. Message content locks only after approval, real queue build, or real sending. If content is locked, duplicate the campaign to create a new editable draft.
Simple mode is best for daily operators. Advanced JSON is for exact WAHA Plus payload control. Both modes render variables before sending.
| Type | Simple composer values | WAHA endpoint | Notes |
|---|---|---|---|
| Text | Text template | /api/sendText | Safest and easiest message type. |
| Image | Media Gallery image or remote image URL, optional caption | /api/sendImage | JPEG/JPG is recommended for WhatsApp compatibility. |
| Voice / Audio | Media Gallery audio asset | /api/sendVoice | Use short, clear audio for test runs first. |
| Video | Media Gallery video asset, optional caption | /api/sendVideo | Keep files small enough for reliable WhatsApp delivery. |
| File / Document | Media Gallery document/file asset, optional caption and filename | /api/sendFile | Use for PDFs, documents, or generic attachments. |
| Poll | Poll name and 2-12 options | /api/sendPoll | Good for simple preference testing. |
| Location | Latitude, longitude, optional title and address | /api/sendLocation | Use exact coordinates for store/location messages. |
| Contact | Structured contact or vCard | /api/sendContactVcard | Use for sharing a support or sales contact. |
| Message + Buttons | Header, body, footer, 1-4 buttons, fallback text | /api/sendButtons | Experimental. If WAHA returns 501, fallback text/poll is sent automatically. |
| Image + Text + Buttons | JPEG media asset or JPEG remote URL, header, body, footer, 1-4 buttons, fallback text | /api/sendButtons | Experimental. Same fallback behavior, plus JPEG header media. |
| List | Advanced JSON only | /api/sendList | Experimental. WAHA says list messages can stop working and should be tested carefully. |
| Variable | Meaning | Example output |
|---|---|---|
{{first_name}} | Contact first name or Test User name. | Preview Name |
{{brand}} | Selected campaign brand. | vaseline |
{{city}} | Imported contact city. Blank for Test Users unless added later. | Preview City |
{{source}} | CRM/source field from the imported contact. | store |
{{store_id}} | Store id/source store value when available. | GOM-12 |
Missing values render blank. Unknown variables are not a feature; keep templates to the supported variables above.
Simple text:
{{first_name}}, edit this {{brand}} message for {{city}} before sending.
Image caption:
{{first_name}}, edit this {{brand}} image caption for {{city}}.
Image + Text + Buttons:
Header: {{brand}} header
Body: {{first_name}}, edit this {{brand}} button message for {{city}}.
Button 1: Option 1
Button 2: Option 2
Fallback: {{first_name}}, edit this {{brand}} fallback text before sending.
Button fallback: WAHA may return 501 for buttons because the feature is deprecated/fragile. For One-Time Preview, test runs, and real worker sends, the app will automatically send the saved fallback text or poll when that specific 501 happens. Other WAHA errors still follow normal retry/failure handling.
Advanced JSON is for exact control over a supported WAHA Plus payload while staying inside safe allowlisted endpoints. It does not execute arbitrary WAHA paths.
Choose Advanced JSON, select a grouped sample, click Load Sample, edit type and payload, then use Preview JSON and One-Time Preview before approving the campaign.
| Sample | Endpoint | Use |
|---|---|---|
text_basic | /api/sendText | Personalized plain text. |
text_link_preview | /api/sendText | Text with WAHA link preview fields. |
text_reply_or_mentions | /api/sendText | Shows optional reply/mention fields for editing. |
image_url | /api/sendImage | Image from a public JPEG URL. |
image_media_gallery, voice_media_gallery, video_media_gallery, file_media_gallery | Media endpoints | Use the selected Media Gallery asset and render caption/filename fields. |
poll | /api/sendPoll | Recommended interactive fallback with 2-12 options. |
location | /api/sendLocation | Store/location style campaign. |
contact_vcard | /api/sendContactVcard | Structured support/sales contact. |
message_buttons_experimental | /api/sendButtons | Message body/footer/buttons without image header, with automatic text fallback on 501. |
image_buttons_experimental | /api/sendButtons | Image header plus editable body/footer/buttons using a Media Gallery JPEG, with automatic fallback on 501. |
buttons_experimental | /api/sendButtons | Legacy message-buttons sample kept for compatibility. |
list_experimental | /api/sendList | Fragile in WAHA. Direct-chat testing is required. |
{
"type": "poll",
"payload": {
"poll": {
"name": "Edit this {{brand}} poll question for {{city}}",
"options": ["Option 1", "Option 2"],
"multipleAnswers": false
}
}
}
{
"type": "location",
"payload": {
"latitude": 0,
"longitude": 0,
"title": "{{brand}} location title",
"address": "{{city}}"
}
}
{
"type": "file",
"payload": {
"filename": "brochure-{{brand}}.pdf",
"caption": "{{first_name}}, edit this {{brand}} file caption for {{city}}."
}
}
{
"type": "buttons",
"experimental": true,
"fallback": {
"type": "text",
"payload": { "text": "{{first_name}}, edit this {{brand}} fallback text before sending." }
},
"payload": {
"button_mode": "message_buttons",
"header": "{{brand}} header",
"body": "{{first_name}}, edit this {{brand}} button message for {{city}}.",
"footer": "Edit this footer or remove it.",
"buttons": [
{ "type": "reply", "text": "Option 1" },
{ "type": "reply", "text": "Option 2" }
]
}
}
{
"type": "buttons",
"experimental": true,
"fallback": {
"type": "text",
"payload": { "text": "{{first_name}}, edit this {{brand}} fallback text before sending." }
},
"payload": {
"button_mode": "image_buttons",
"header": "{{brand}} header",
"headerImage": {
"mimetype": "image/jpeg",
"filename": "{{brand}}-header.jpg",
"url": "https://example.com/{{brand}}.jpg"
},
"body": "{{first_name}}, edit this {{brand}} button message for {{city}}.",
"footer": "Edit this footer or remove it.",
"buttons": [
{ "type": "reply", "text": "Option 1" },
{ "type": "url", "text": "Open link", "url": "https://example.com/{{brand}}" }
]
}
}
{
"type": "list",
"experimental": true,
"fallback": {
"type": "poll",
"payload": {
"poll": {
"name": "Edit this {{brand}} fallback poll question",
"options": ["Option 1", "Option 2"]
}
}
},
"payload": {
"message": {
"title": "{{brand}} list title",
"description": "{{first_name}}, edit this {{brand}} list description.",
"button": "Open list",
"sections": [
{
"title": "Options",
"rows": [
{ "title": "Option 1", "rowId": "option_1" }
]
}
]
}
}
}
Bad examples:
- {"endpoint": "/api/deleteSession"} is blocked because endpoints are not arbitrary.
- {"type": "button"} is blocked; the allowlisted type is "buttons".
- {"type": "buttons", "payload": {"body": "Hi", "buttons": [{"type": "payment"}]}} is blocked.
- {"type": "image", "payload": {"url": "promo.png"}} should be tested carefully; JPEG/JPG is preferred.
Test-first rule: Load Sample -> edit JSON -> Preview JSON -> One-Time Preview -> worker Test Run -> approve only if stable. Buttons and list are experimental, so poll or text is the recommended fallback for production campaigns.
Blocked in v1: arbitrary endpoints, operational endpoints such as edit/delete/star/reaction/forward/status/channel sends, and event messages. Event messages are reserved for appointment-style RSVP flows.
Use Media Gallery for reusable image, audio, video, and document files. Uploads store metadata in media_assets and files in the Docker media volume.
PUBLIC_MEDIA_BASE_URL is empty, the worker reads the file and sends WAHA file.data at send time.For local development with remote WAHA, the dashboard URL may not be reachable from WAHA. In that case the base64 fallback is important: the worker sends the uploaded file data directly instead of asking WAHA to download a local URL.
| Feature | What it does | Does it consume limits? | Does it create real queue rows? |
|---|---|---|---|
| Test User | Internal recipient with full international phone number. Not a CRM contact. | No by itself. | No. |
| Test List | Reusable group of active Test Users, usually 10-50 people. | No by itself. | No. |
| One-Time Preview | Immediate send of current editor content to one active Test User. | It uses WAHA immediately but does not create a worker test run. | No. |
| Worker Test Run | Snapshots a Test List into test-run recipients and lets the worker send slowly. | Yes. It uses the same WAHA session limits as real sending. | No. |
| Real Campaign Queue | Builds rows from imported Contacts for production sending. | Yes. | Yes. |
One-Time Preview sends the current Campaign Builder editor content immediately to one active Test User. It can save first or send unsaved editor content. It does not create a test run and does not create real queue rows.
Worker Test Run starts from Campaign Testing. It snapshots a Test List into campaign_test_run_recipients and uses the same WAHA session, sending window, daily/hourly limits, delay, pause, max-attempt rules, worker logs, and reports as real sending.
Snapshot rule: editing a DRAFT after a test run does not change old test-run logs. Old logs keep the rendered payload that was sent at that time.
Reports are downloadable as CSV and JSON from campaign test-run history. Logs include message type, endpoint, status, attempts, WAHA message id, errors, timestamps, and payload summary.
Queue rendering: real campaigns and test runs store the rendered endpoint and rendered_payload. The worker sends that generic payload instead of branching only on text/image.
Real queue building excludes invalid, suppressed, opted-out, or duplicate contacts. Each contact receives one outbound marketing message per campaign unless a future reply flow is added.
Settings guide new campaigns and the Docker worker. Keep limits conservative for new WhatsApp sessions. No setting can guarantee that WhatsApp will not ban or restrict a session.
| Setting | Default/current guide | What it controls | Safe recommendation | When to change |
|---|---|---|---|---|
daily_limit | 30 | Maximum messages per WAHA session per day. | 30 for new, 60 warming, 100 warm. | Raise only after stable delivery and low failures. |
hourly_limit | 25 | Maximum messages per WAHA session per hour. | 10-15 new, 15-20 warming, 20-25 warm. | Lower if sends feel bursty or failures rise. |
sending_window_start | 09:00 | Earliest local time the worker may send. | Use normal customer hours. | Change for local business hours only. |
sending_window_end | 17:30 | Latest local time the worker may send. | Avoid night sending. | Extend only for approved use cases. |
sending_days | 1,2,3,4,5,6 | Allowed ISO weekdays, where 1 is Monday and 7 is Sunday. | Monday-Saturday. | Remove weekends if complaints increase. |
delay_min_seconds | 45 | Minimum random delay after a send. | 60 for cold sessions, 45 for warm sessions. | Increase for safer pacing. |
delay_max_seconds | 120 | Maximum random delay after a send. | 120-180 for cold sessions. | Increase for larger/cautious campaigns. |
pause_after_sends | 25 | Worker pauses after this many sends in a block. | 25 or lower. | Lower for cold sessions. |
pause_min_minutes | 10 | Minimum random pause after a send block. | 10-20 minutes. | Increase if sending many hours per day. |
pause_max_minutes | 20 | Maximum random pause after a send block. | 20+ minutes for safer pacing. | Increase for cautious warmup. |
max_attempts | 3 | Attempts before a recipient becomes failed. | 2-3. | Lower if WAHA errors are permanent. |
poll_interval_seconds | 45 | How often the worker wakes when nothing is immediately sendable. | 30-60 seconds. | Lower for faster dashboard feedback, higher for quieter DB usage. |
timezone | Africa/Kinshasa | Timezone used for sending window and days. | Match recipient country/timezone. | Change before launching campaigns in another region. |
| Session stage | Daily | Hourly | Delay | Use when |
|---|---|---|---|---|
| New/cold | 30/day | 10-15/hour | 60-180 seconds | New sessions or unknown account health. |
| Warming | 60/day | 15-20/hour | 60-150 seconds | After low failures and normal replies. |
| Warm | 100/day | 20-25/hour | 45-120 seconds | Tested sessions only. Higher volume is high risk. |
The dashboard changes campaign state; the Python worker performs actual background sending. No host cron is needed because the Docker worker loop replaces cron for v1.
| Status | Meaning | What to do |
|---|---|---|
DRAFT | Campaign is editable and not approved. | Preview, test, edit, then approve when ready. |
APPROVED | Message content is approved and locked. | Build queue when audience is ready. |
PAUSED | Campaign or test run is stopped from sending new rows. | Check reason, resume/start when safe. |
RUNNING | Worker may claim rows if limits and window allow. | Monitor Running Campaigns and Worker Logs. |
COMPLETED | All sendable rows have finished. | Download reports and review failures. |
CANCELLED | Campaign/test run was intentionally stopped. | Duplicate if a new version is needed. |
Recipient rows may be PENDING, PAUSED, SENDING, SENT_API_ACCEPTED, DELIVERED, READ, FAILED, or SKIPPED. WAHA webhook acknowledgements update delivered/read states when available.
worker_events records for claim, sent, failed, paused, retry, and worker error events.DASHBOARD_API_TOKEN, DATABASE_URL, SUPABASE_URL, Supabase service/import key, WAHA_BASE_URL, and WAHA_API_KEY in the project environment.PUBLIC_MEDIA_BASE_URL empty for local/remote mismatch so base64 fallback is used, or set it to a public HTTPS URL that WAHA can fetch./api/webhooks/waha with events message, message.any, and message.ack.localhost, remote WAHA cannot call it. Set PUBLIC_DASHBOARD_BASE_URL to the public HTTPS dashboard URL or use a tunnel for development.WORKING before real sending.Redis is not required for v1. Postgres is enough for 10,000 queued users because sending is intentionally slow and queue claiming is database-backed. Consider Redis later only for multiple API replicas, high-volume realtime pub/sub, or cross-process coordination that Postgres cannot handle comfortably.
campaign-sender/worker/campaign_worker/ sends generic WAHA payloads, resolves media file data, applies limits, and writes worker events.campaign-sender/lib/messageTemplates.js renders simple and advanced templates into { endpoint, payload }./api/events/stream streams dashboard summaries, worker status, notifications, and recent worker events with polling fallback in the UI./api/media, /api/media/:id, /api/media/:id/file, and delete endpoints manage metadata and uploaded files./api/template-samples, /api/templates/preview, and /api/templates/send-test power preview and one-time test sends./api/test-runs, /api/test-runs/:id, /api/test-runs/:id/report.csv, and /api/test-runs/:id/report.json.media_assets, campaigns.template_mode, template_json, media_asset_id, send_queue.endpoint, and rendered_payload./api/webhooks/waha stores raw events and updates real queue rows or test-run recipients by WAHA message id/chat id where possible.Maintainer: Vivek M, India, +917972495890
| Problem | Likely reason | What to check |
|---|---|---|
| Login screen appears after refresh | Stored token missing or invalid. | Enter the current dashboard token. If it fails, verify DASHBOARD_API_TOKEN. |
| WAHA session missing | WAHA offline, API key wrong, or no WORKING session. | Open Overview connections, refresh sessions, verify WAHA_BASE_URL and WAHA_API_KEY. |
| Worker not sending | Campaign paused, outside sending window, daily/hourly limit reached, no queue rows, or worker offline. | Open Running Campaigns, Worker Logs, Settings, and Worker Status. |
| Campaign stuck outside window | Configured timezone/window/day blocks sending. | Check Settings timezone, start/end time, and sending days. |
| Audience count looks low | Preview filters, brand selection, banned users, invalid phones, or not imported yet. | Use Audience live preview and Contacts summary separately. |
| Media sends fail | WAHA cannot fetch local URLs or file type/size is unsupported. | Use Media Gallery, keep PUBLIC_MEDIA_BASE_URL public or rely on base64 fallback, and test with One-Time Preview. |
| File upload does not open picker | Browser/cache/UI issue. | Use the visible native file input, hard refresh, or drag-and-drop into Media Gallery. |
| Webhook statuses not updating | WAHA webhook not configured or message id cannot be matched. | Check /api/webhooks/waha configuration and message_events/Worker Logs. |
| Content cannot be edited | Campaign is approved or queue was built. | Duplicate the campaign to create a new editable draft. |