Digital Marketing
Digital Marketing sends one message to a list of people, and then tells you what happened to it. You upload the list as a segment, point a campaign at it and at a message template held in the Unified Contact Center, and execute it, now or for a scheduled moment. It posts nothing to the books.
This is the reference for whoever runs campaigns and whoever is asked to explain their figures.
Your job, and the screen for it
| The job | Screen | Read |
|---|---|---|
| Load the people you will message, in the column order the importer expects | Segment › Segment Listing › upload | A segment is a file you uploaded |
| Write the message | Template Message (the contact centre’s templates) | Two different things are called a message template |
| Send it now, or queue it for a time | Campaign › edit › Run Campaign › Run now or Schedule | What happens when you execute a campaign; Scheduling queues now and sends later |
| Read how it went | Campaign › edit › Refresh Summary; Report › Campaign Report | The six counters |
| Ask whether it sold anything | Report › Sales Tracking Report | The sales-tracking report |
Overview
BigLedger fans a campaign out to one queued message per phone number or e-mail address, hands each to WhatsApp Business or to e-mail broadcast, and records an execution row for every one handed over. It is built to fold WhatsApp’s status callbacks back into a per-campaign tally and an optional sales-tracking report that looks for purchases by the people you messaged; read the callout on the counters before you rely on either.
What it is not is a marketing-automation suite. There is no rule you can write that finds your customers for you, no multi-step journey, no A/B split, no open or bounce tracking on e-mail. The sections below say exactly where each of those stops, because the database carries tables for several of them and the API carries routes for more.
Where it fits
Digital Marketing sits in the CRM & Digital module and depends on the contact centre for everything that actually touches a recipient.
| It needs | From | Why |
|---|---|---|
| The message template a campaign sends | Unified Contact Center | A campaign sends a contact-centre template, using its lines in the campaign’s language. An inactive template queues nothing; a template with no lines in that language queues messages with no content |
| The endpoint the message leaves from | Unified Contact Center | The campaign’s endpoint supplies the sender identity. Which channel each message goes by is decided by the segment line’s CHANNEL_TYPE, not by the endpoint |
| Delivery, read and reply status | WhatsApp Business callbacks, through the contact centre’s WhatsApp message handler | Nothing else in the product writes these. E-mail broadcast never reports back |
| Customer and entity records | Contact and entity master data | Used only by the sales-tracking report, to match a recipient back to a customer. Campaigns themselves never look at your customer records |
| Uploaded images and attachments | The platform file service | Handed over as signed links that expire after 48 hours. See Media links expire after two days |
Nothing downstream depends on Digital Marketing. It posts no journal, moves no stock and changes no customer record. A campaign is a read of your data and a send; deleting one changes nothing else in BigLedger, and does not stop messages it already queued (Cautions).
Screens and menus
The applet’s menu has six entries:
- Project — a folder that campaigns, segments, channels and templates can be grouped under.
- Campaign — the list of campaigns, and the create/edit form with Run Campaign (execute now, or Schedule) and Refresh Summary.
- Schedule — dated, timed entries a campaign can be linked to.
- Segment — Segment Listing and Unsubscribed Listing.
- Report — Project Report, Campaign Report, Sales Tracking Report.
- Template Message — the contact centre’s message templates, the ones a campaign sends.
The settings menu lists Field Settings and Default Selection; the applet also serves a webhook page and the five permission listings. Personalization holds a personal default selection.
What the API has and the screen does not
The backend carries 232 REST routes for this applet, and the screens reach a minority of them. Treatments, channels, criteria, dimensions, labels, segment resources, segment conditions and recurring campaigns have full create, read, update and delete routes and no menu entry in the applet. If you have read about any of those in a database dictionary or an API listing and gone looking for them in the applet, that is why you could not find them. Two of them, treatments and segment conditions, have no engine behind them either; What this applet will not do says which.
Configuration
Before you can use it, three things have to exist outside this applet:
- A contact-centre endpoint for WhatsApp Business or e-mail broadcast, configured and working. See Unified Contact Center.
- A message template in the contact centre, approved on the channel it will be sent through, with lines in the language the campaign uses.
- A clock on the campaign-processing job, if you use scheduled sends. Without one, scheduled messages wait in the queue until some other campaign is executed. Clocks are added in the Scheduler applet where its picker offers the code; where it does not, ask BigLedger.
Applet settings
The settings hold Field Settings, Default Selection, a webhook page and the five permission listings, plus a personal default selection under Personalization. No setting was found that changes what a campaign sends or when. The 48-hour media links, the 28-day sales-tracking window, the priority and the retry counts are constants in the backend, and the 1,000-message send limit is the default of a parameter on the execute call, not a setting. The settings mechanism itself is described once at Settings and permissions.
Who can change what
Permissions are declared as 375 codes, all prefixed API_TNT_DM_DGMKT_, following one pattern:
seven verbs — owner, admin, member, create, update, delete, read — against each of about 53 objects.
Two consequences for whoever assigns them:
- The granularity is far finer than the screen. Most of the 375 guard routes no menu entry reaches, so granting or withholding them changes nothing a user sees on a screen.
- Executing a campaign, refreshing its summary and generating its sales-tracking report all need the campaign create permission. There is no separate “may send” right, so anyone who can build a campaign can send one.
Fields
The upload file is the one form whose fields decide what a campaign does. Its four columns, in this order:
| Column | What it holds | What goes wrong |
|---|---|---|
CHANNEL_TYPE | The channel this line is sent on: WHATSAPP_BUSINESS or EMAIL_BROADCAST. It, not the campaign’s endpoint, decides the channel | An unrecognised value rejects the whole upload; a channel other than the two that have a gateway fails at the send |
ENDPOINT_ID | The phone number or e-mail address | Empty rejects the whole upload; repeated is dropped silently, compared as typed |
FIRST_NAME | The only per-recipient text a template can use, through the customer-name placeholder | — |
LAST_NAME | Passed to the gateway with a WhatsApp message; never substituted by BigLedger | — |
The campaign form names the project, the channel, the endpoint, the template message and its language; the schedules are linked on its Schedules tab.
Lifecycle and effects
A campaign posts nothing and has no statuses to move through; its lifecycle is the send. The three paths below are execute now, schedule, and the recurring job.
What happens when you execute a campaign
None of this is visible from the campaign screen, and all of it decides what your recipients get.
Run Campaign › Run now calls the campaign’s execute endpoint, and five things follow in order.
- BigLedger expands the campaign into one queued message per endpoint. One database statement walks the campaign’s active segments and their active lines and writes a queued message for each. Two filters apply that the screen does not show: lines are de-duplicated on the endpoint text (the number or address as stored), so a number that appears in three of the linked segments is messaged once, but a person listed under a phone number and an e-mail address gets two messages; and only lines marked valid are taken, which is where most empty sends come from (see below).
- The message text is frozen into the queued message, now. The recipient’s name, the template, the language, the resolved placeholder text and the header and body are built into the queued message at this moment. Editing the template afterwards changes nothing about a campaign already executed. Media are stored as file references and signed only when each message is processed.
- BigLedger writes the campaign’s unique contact list, one row per recipient, status
PENDING, unique on campaign + channel + endpoint. This is the roster the sales-tracking report later works from, and its creation time is where that report’s window starts. - The send job starts, but only if step 3 added at least one new row. It picks the queued messages that are due, not expired and have retries left, by priority then start time, and fires one per-message job for each. It takes up to 1,000 by default, from the whole queue rather than from your campaign.
- Each message is signed, built and posted. The per-message job replaces the media reference with a link valid for 48 hours, builds the channel’s request, and posts it to an AWS API Gateway campaign-processing queue. Only then does it write one execution row for the campaign.
Read step 5 twice if you are trying to reconcile a number. An execution row means BigLedger handed this one over, never that it arrived. A message whose channel is neither WhatsApp Business nor e-mail broadcast fails before the hand-over: the job is retried until its retries run out, an error is logged for the scheduler, and no execution row appears.
Scheduling queues now and sends later
Run Campaign › Schedule does the same expansion, but takes each queued message’s start time from the schedules linked to the campaign, so every message is queued at once with a future start time — once per linked schedule, so two linked schedules send each person two messages. It then runs the send job once, immediately, and that run skips everything not yet due.
Nothing comes back at the scheduled time on its own. The messages go out the next time the send job runs after their start time: on a clock that runs Digital Marketing’s campaign-processing job on the tenant, or when somebody executes another campaign. That clock needs its Json filled in (the request address, the tenant code and a limit above zero), which the job’s own defaults leave empty, so ask BigLedger to set it up rather than adding it from the picker alone. A scheduled campaign on a tenant with no such clock waits in the queue until one of those happens. Linking a schedule to a campaign and waiting, without pressing Schedule, queues nothing at all.
Recurring campaigns
The recurring-campaign job reads the recurring-campaign table and, for every row that has not yet
produced one, clones it into a real campaign, stamps the new campaign’s GUID onto the recurring row and
marks it POSTED. A row that has generated its campaign is never picked up again, and the job applies
no date filter.
The job only creates the campaign; nothing sends it. No execute or schedule call follows, and the schedule is copied onto a header field the scheduled-send path does not read (that path reads campaign-schedule links, and none is created). So a generated campaign sits unsent until someone opens it and runs it. The recurring-campaign queue table beside it is declared and not used; the job’s own comment says the queue will come “later when it grows big”.
Where each number comes from
This is the section to read when a figure looks wrong.
A segment is a file you uploaded, frozen at the moment you uploaded it
There is no query behind a segment. A segment’s lines come from a CSV you upload, and the importer reads exactly four columns, in this
order: CHANNEL_TYPE, ENDPOINT_ID, FIRST_NAME, LAST_NAME. Your file’s first row is skipped as a
header whatever it says, so a file with its columns in another order imports into the wrong fields.
Nothing recomputes the list afterwards. If a customer changes their number, unsubscribes or buys nothing
for three years, the segment does not know. Its age is the age of the upload.
What the upload does that no message explains:
| At upload | What happens | Why it matters |
|---|---|---|
A row with an empty ENDPOINT_ID, an unrecognised CHANNEL_TYPE, or fewer than four columns | The whole upload is rejected; nothing is imported | Clean the file first; one bad row costs the whole list |
A repeated ENDPOINT_ID | Dropped, first occurrence kept. The comparison is on the text as typed, so +60 12-345 6789 and +60123456789 both stay | The row counts differ with no explanation, and two spellings of one number reach the send, where they collapse to one message |
CHANNEL_TYPE is WHATSAPP_BUSINESS | The number is checked and marked valid or invalid, with the reason stored on the line | This is the only channel the importer validates, and the only one it ever marks valid |
0,
no queue rows are written, no error is raised, and the campaign looks executed. The e-mail path is
otherwise built — the per-message job has a complete e-mail-broadcast branch — so this is a gap between
two working halves. Until it is fixed, an e-mail campaign needs its segment lines created through the
segment-line API with the valid flag set, not through the upload screen.Personalisation reaches exactly one field
When the expansion fills a template placeholder, the only per-recipient source it recognises is the customer-name placeholder, which it fills from the segment line’s first name. Everything else falls back to the placeholder’s fixed value, then its default, the same for every recipient. BigLedger never substitutes the last name; it passes it to the gateway with a WhatsApp message, and BigLedger’s code does not show what the gateway does with it. An e-mail’s body is the template text with no placeholder filled at all.
So a WhatsApp template can say “Hi {{name}}”. It cannot say “your last order was …”, because nothing in the send path reads an order.
The six counters on a campaign are recomputed when someone asks, and then written down
processed, not sent, sent, delivered, read and replied are stored on the campaign header,
and they are not a running tally. Pressing Refresh Summary counts that campaign’s execution rows six
ways, writes the six answers onto the header, and shows them. The button
needs the campaign-create permission.
So the numbers on a campaign header are as old as the last time somebody pressed Refresh Summary. If a campaign was executed on Monday and nobody refreshed it until Friday, Monday’s numbers sat on the record all week. The numbers are never wrong; they are answers to a question asked at a moment the screen does not show.
Delivered, read and replied are WhatsApp-only, and may stay at zero on WhatsApp too. The only thing that sets them is the contact centre’s WhatsApp message handler, reacting to WhatsApp’s status callbacks, and a sent callback is meant to mark a message delivered.
But the handler looks the execution row up by the campaign’s own id rather than by the row’s id, so, unless the gateway sends the two the other way round, a callback updates no row. We have found no execution row on any tenant marked delivered or failed. Until that is settled, treat delivered, read and replied as unavailable on both channels, and the sales-tracking report (which starts from delivered contacts) as empty. An e-mail-broadcast campaign leaves all three at zero by design.
The sales-tracking report is a stored snapshot over a 28-day window from the run
The sales-tracking report asks: did the people we messaged buy anything afterwards? It is generated on demand, runs through its own queue and job, and inserts rows into a report table. It is a snapshot, not a view.
How it arrives at its answer, in order:
- It starts from the campaign’s unique contact list, restricted to contacts whose status is
DELIVERED. BigLedger sets that on any WhatsApp status callback, a failure included, and never for e-mail. So an e-mail campaign always produces an empty report, and a WhatsApp one can include people the message never reached. - For each contact it looks for a customer, and it only finds one through an active CRM contact whose
ID number equals the recipient’s endpoint, in one of three forms: as given, without the
+, and with the first two characters dropped (for+60…, the local0…form). From that contact it reaches the customer by phone, e-mail, contact key or entity link. - It pulls that customer’s sales lines — cash bills, sales invoices and sales orders only — with a non-zero amount and quantity, dated from when the campaign was run (the contact-list row’s creation time: Run now, or the moment you pressed Schedule, not the later send) to the end of the window.
- The window ends 28 days later unless you pass an end date.
Three limits follow. A walk-in customer who pays cash and is not matched to a contact record cannot appear, because step 2 cannot find them. A purchase on any other document type is not counted. And because both sales orders and the invoices raised from them count, one purchase can be counted twice — an inference from the filter, not something we have tested.
Re-running the generation appends a fresh set of rows; nothing removes the previous run. Compare generations by their creation date.
What this applet will not do
Stated plainly, because each of these has a table, a route family or a menu entry that suggests otherwise.
It will not find your customers for you. There is no rule-based segment. Segment conditions, criteria, and operator and value types all exist in the database and the API, and the one piece of code that reads them, the segment report, builds its SQL in a stream that is never run, so the conditions are never evaluated and the statement it produces is not valid SQL. It has not changed since 2022. A segment is a file you upload.
It will not run a treatment. A treatment — “send this template to this segment on this schedule through this endpoint” — has a header table, an execution table, four controllers, a full CRUD route family and 42 permission codes. Nothing executes one: no job, no scheduler, and no service writes a treatment execution row except the plain REST endpoint you would call yourself. The table and its API date from late 2021 and the execution endpoint from early 2023; the engine never followed.
It will not run a journey, a drip or an A/B test. One campaign is one message to one list at one moment. Sequencing, branching on behaviour, hold-out groups and variant splits have no representation anywhere in the applet.
It will not send on any channel but two. WhatsApp Business and e-mail broadcast are the only channels with a gateway. A segment line on any other channel fails at the send and writes no execution row.
It will not tell you an e-mail bounced, opened or was clicked. Nothing in the product takes in e-mail delivery events.
It will not check your unsubscribed list at send time, as far as we can establish. The unsubscribed-numbers table and its screen exist, and the campaign expansion does not join to them, nor to the segment line’s own unsubscribed flag: a number on the list is queued like any other. The message then leaves BigLedger through an AWS gateway whose behaviour we cannot read, so we cannot say whether suppression happens there. This is unresolved and we flag it rather than guess. Until it is settled, keep unsubscribed numbers out of the CSV you upload, and do not rely on the list as your only consent control.
Cautions
Media links expire after two days
The media a campaign message carries — the header image, video or document on WhatsApp, the attachments on e-mail — is handed to the gateway as a signed link that expires 48 hours after the message was processed, not 48 hours after the recipient opened it. An e-mail sends its body text and its attachments only; image, video or document components of the template are not included. The code carries a note saying a public endpoint should be built instead.
Whether a WhatsApp recipient still sees the media after two days depends on whether the gateway hands the file over at the send. For e-mail, whether a recipient who opens the message on day three still sees the file depends on whether the gateway attaches the file or sends the link, and BigLedger’s code does not show which. Send yourself the test e-mail described below and open it again after two days before you rely on images in an e-mail campaign.
Executing the same campaign twice queues it twice and may send nothing
The queue rows are written every time, but the send job is started only when the unique contact list gains a new row, and on a second execute of the same campaign it gains none. The result is duplicate messages sitting in the queue, unstarted, that go out the next time the send job runs: another campaign’s execute, a clock, or a manual run.
Do not press execute twice to “make sure it sent”. Check the execution rows instead.
Which duplicate wins is not decided by anything you can set
The de-duplication on the endpoint has no tie-break. If the same number sits in two linked segments under different first names, or against different channel types, the row that survives — and so the name your recipient is greeted by and the channel they are reached on — is arbitrary and may differ between runs. Keep a person in one segment per campaign.
Two different things are called a message template
The Template Message menu edits the contact centre’s templates, which are the ones a campaign sends. The applet’s own message-template routes, with a table of their own, are not read by campaigns. If you build templates through the API, build them in the Unified Contact Center’s template family.
Deleting a campaign does not stop messages already queued
Each queued message carries its own frozen copy of the send, and the send job does not check that the campaign still exists. Deleting a scheduled campaign therefore does not stop the messages it queued; they go out the next time the send job runs after their start time.
Anyone who can create a campaign can send any campaign
Execute checks the campaign-create permission (admin and owner also accepted), and the check is not tied to the campaign: whoever holds it can execute any campaign in the tenant, to everybody on its segments.
The part that is still your job
A campaign is a business decision with a send attached, and the software does the send. Everything below is a person’s work, before or after the button.
You decide who is on the list, and nothing checks you
Because a segment is an uploaded file, the list is exactly as good as whoever built the file, and the applet offers no opinion on it. Before you upload:
- Get the list from a query someone can show you again, an export with its filter recorded, rather than a spreadsheet forwarded twice. When the results are questioned in a month, the question will be who was on it, and the segment will not tell you why.
- Take the unsubscribed numbers out yourself. The applet may not do it, and the consequence of being wrong is a complaint, not an error message.
- Put the four columns in order, write numbers one way, and compare the segment’s line count to your file’s row count after the upload. It takes ten seconds and it is the only check there is.
You approve the send, because the product has no approval step
There is no draft state, no reviewer and no second pair of eyes. Permission decides who may execute, not whether anyone looked. Whatever approval your business wants happens before someone with that permission presses the button, in a meeting or a message, not in BigLedger.
The practical minimum: send the campaign to a segment containing only yourself first, on the real channel, and read the message on a real handset. It is the only way to see the rendered result, including whether the media loads.
You choose the window the results are judged over
The sales-tracking report defaults to 28 days from the run, and 28 days is a number in the code, not a finding about your customers. For a phone, 28 days is generous; for an appliance, it may be too short. Decide the window that matches how long your customers take to buy what you advertised, pass that end date, and use the same length every time you compare two campaigns — otherwise you are comparing windows, not campaigns.
You reconcile attribution, because the report cannot
The report credits a campaign with any qualifying sale by a matched customer inside the window. It does not know whether the message caused it, and it cannot see a purchase by someone it could not match to a contact record — which, in a retail business, is most walk-in cash customers. Before anyone acts on the figure, someone has to say what share of sales is matchable at all. A useful habit is to keep a small part of the list unsent, look up what those people bought over the same window by hand, and compare: that is the baseline the report cannot give you.
You notice that nothing arrived
Nothing raises an alarm when a campaign sends to nobody. Execute returning 0 is a successful response.
Messages waiting in the queue are not shown. An e-mail campaign’s delivered count of zero looks the same
as the feature not working.
The person who ran the campaign has to look, within the hour:
- Did the campaign produce execution rows at all? Zero means the expansion matched nobody — almost always the valid-line filter or an inactive template.
- Does the execution count match the segment’s valid line count, allowing for duplicates removed?
- Ask a few people on the list whether the message arrived. The delivered count may never move (see the counters callout), so it cannot tell you.
- For a scheduled campaign, check again after the start time: if nothing moved, the send job has not run since.
Related applets
- Unified Contact Center — owns the endpoints and the message templates every campaign uses, and is the only source of delivery, read and reply status. A campaign is unusable without it. For one message to one list, sent now, its Broadcast screen is the simpler tool.
- Engagement Applet — a client register for an audit or tax practice, not a marketing tool, despite the name.
- Bot Applet — conversational flows on the same contact-centre channels. Campaigns push; bots respond.
Troubleshooting
| Symptom | Cause | What to do |
|---|---|---|
| The upload fails and nothing is imported | One row has an empty ENDPOINT_ID, an unrecognised CHANNEL_TYPE, or fewer than four columns | Fix the row and upload again; the importer takes the whole file or none of it |
Execute returns 0 and nothing is queued | No matched segment line is marked valid — on a CSV-uploaded e-mail segment, all of them; on WhatsApp, the numbers that failed validation — or the template, a segment or the campaign is not active | Read the reason stored beside each invalid line. For e-mail, create the lines through the segment-line API instead of the upload screen |
| A scheduled campaign never sends | The messages are queued for later, and the send job has not run since their start time | Add a clock on the campaign-processing job in the Scheduler, or wait for the next campaign execute |
| A recurring campaign was created but never sent | The recurring job only creates the campaign | Open the generated campaign and run it |
| Delivered / read / replied stay at zero | On e-mail, nothing reports back. On WhatsApp, the callback looks the execution row up by the wrong id | Use the execution row count, and ask recipients, until the WhatsApp callback is settled |
| The sales-tracking report is empty | Its list is limited to DELIVERED contacts, which only WhatsApp sets; or no active CRM contact carries the recipient’s number as its ID number; or the purchases fall outside cash bills, sales invoices and sales orders | Check the delivered count first. If it is zero, the report will be empty |
| Campaign counters look stale | They are recomputed only on Refresh Summary | Press Refresh Summary |
| Duplicate messages arrive later, apparently at random | A second execute queued them without starting the send job; a later run sent them | Do not re-execute. Check execution rows to confirm a send |
| A recipient is greeted by the wrong name | They appear in two linked segments with different first names, and the de-duplication has no tie-break | Keep each person in one segment per campaign |
| A message on a channel other than WhatsApp Business or e-mail never goes and leaves no execution row | Only those two channels have a gateway | Use one of the two channels on the segment line |
Related documentation
- CRM & Digital module — where this applet sits and what else is in it.
- Digital CRM in Depth, What the module has no engine for — the campaign limits in the context of the whole module.
- Unified Contact Center — endpoints, templates and the message handler that reports delivery.