Settings
Create, Preview, Test, Send and Schedule Customer Campaigns
Campaigns send approved SMS or WhatsApp messages to audiences selected from customer and loyalty data. Preview is read-only; sending and scheduling create durable campaign and recipient records.
- Menu path
- Manage -> Marketing -> Campaigns
- Verified from
- Posnic demo and current POS source reviewed on 2026-09-13
Technical source evidence
Live Posnic demo reviewed 2026-09-13, frontend/modules/settings_write.html Campaigns tab, frontend/static/script/js/modules/js/campaigns.js, api/src/services/campaign.service.js, api/src/controllers/campaign.controller.js, api/src/constants/campaign.constants.js, api/tests/unit/services/campaign.service.db.test.js, Messaging integration settings
Understand the Campaign Lifecycle
| Stage | What Posnic does | Operator evidence |
|---|---|---|
| Prepare | Uses customer phone, channel preferences, category, loyalty and purchase history. | Clean customer records and an approved message. |
| Preview | Counts the selected segment and reachable customers without saving or sending. | Reachable of total plus up to five example names. |
| Save | Creates a draft with its channel, message, segment, branch and operator metadata. | Draft row in the campaign list. |
| Send | Rebuilds the audience, renders one message per customer and dispatches through the saved branch provider. | Sent, failed and skipped totals. |
| Schedule | Stores a date/time and marks the campaign scheduled until the due runner processes it. | Scheduled badge and saved time. |
| Audit | Keeps per-recipient outcome records even when the campaign row is later deleted. | Campaign status and support-side send history. |
Prepare Before Composing
- Enable Marketing under Manage -> Features. Campaign creation and real delivery require Branch Write unless that permission is not explicitly denied.
- Configure and test the required provider under Manage -> Messaging. SMS uses the branch SMS provider; WhatsApp uses the shop's linked local WhatsApp device in the current campaign service.
- Clean customer phone numbers, loyalty tiers and points, category assignments, last-purchase dates and per-channel opt-out records before selecting an audience.
- Agree the purpose, audience, message, delivery time and approver outside the send screen.
- Confirm the active branch before creating the campaign because its messaging adapter and audit metadata come from that branch.
- Use Preview reach first. Do not use Send now as a preview.
Choose the Audience Precisely
| Audience | Actual filter | Important boundary |
|---|---|---|
| All customers | Every customer under the current license enters the segment. | Reachable still excludes missing phones and channel opt-outs. |
| Loyalty tier | Exact match on the customer's current loyalty tier; options load from Loyalty configuration. | A blank tier falls back to all customers in the backend query. |
| Minimum points | Current loyalty points greater than or equal to the entered value. | Negative or invalid input is cleaned to zero. |
| Lapsed (no purchase) | Last purchase is older than the calculated cutoff, missing or null. | Zero days includes purchases earlier than the current instant plus customers with no recorded purchase. |
| Customer category | Exact customer category ID match. | The current form asks for the internal category ID, not its display name; a blank value falls back to all customers. |
Create the Campaign Step by Step
- Open Manage -> Marketing -> Campaigns and click New campaign.
- Enter a clear internal Name. Name and Message are mandatory; only WhatsApp or SMS is accepted.
- Choose the Channel whose provider and customer consent records have already been checked.
- Choose the Audience and complete the tier, minimum-points, lapsed-days or category-ID field that appears.
- Write the Message and insert supported merge chips at the cursor where needed.
- Select Preview reach and record the reachable count, total segment count and sample names.
- Correct source customer data or narrow the audience when the preview is unexpectedly broad, narrow or empty.
- Select Save draft when copy, timing or audience still needs approval.
Use Merge Fields Correctly
- The live form exposes chips for {name}, {points} and {tier}; supported backend fields also include {phone}, {balance} and {lifetime} when typed exactly.
- Token matching is case-insensitive, but use the displayed lowercase spelling for review consistency.
- Preview reach does not show the rendered message. Proofread every token and test with representative customer data.
| Token | Rendered value | Empty-data behavior |
|---|---|---|
| {name} | Customer name. | Blank text. |
| {phone} | Stored customer phone. | Blank text. |
| {points} | Current loyalty point balance. | 0. |
| {balance} | The same current loyalty point balance as {points}. | 0. |
| {lifetime} | Lifetime points earned. | 0. |
| {tier} | Current loyalty tier. | Blank text. |
| Unknown token | An unsupported token such as {shop} remains literally in the message. | Never assume it will be removed or populated. |
Preview Reach Before Saving
Preview is the safest read-only audience check. Posnic rebuilds the selected segment, counts every matching customer, then counts a customer as reachable only when a nonblank phone exists and that customer has not opted out of the selected channel.
- The sample contains at most five reachable customers and displays names, falling back to phone only when the name is blank.
- Preview checks that a phone string exists; it does not prove the number is valid or that the provider can deliver.
- Changing Channel can change the reachable count because SMS and WhatsApp opt-outs are independent.
- The audience query is license-wide in the current service, not restricted to the branch that created the campaign. Treat Preview as the authoritative reach count for multi-outlet shops.
Understand the Current Dry-Run Caveat
Test (dry run) first saves the form, renders a message for each reachable customer and writes recipient logs without calling SMS or WhatsApp. However, the current de-duplication query treats a dry-run record as already processed. A later real send of that same saved campaign skips those dry-run recipients.
- A dry run dispatches zero messages and leaves the campaign status unchanged, normally Draft.
- Its result alert reports reachable dry-run rows under skipped rather than sent.
- Do not dry-run the exact campaign record intended for delivery in the current release.
- Safe controlled workflow: preview the final audience, create a temporary test campaign for the dry run, inspect the result, delete that temporary row if approved, then create a fresh campaign record for real send or scheduling.
- Deleting the temporary campaign keeps its recipient send history in the database. Use a clearly labeled test name for auditability.
Save, Edit and Delete Drafts
- Save draft creates a durable campaign row with Draft status and zeroed result totals.
- Open uses the pencil action and loads the row into the same form. Saving updates name, channel, message, segment and branch metadata but preserves the existing status and counters.
- A sent campaign may be opened and edited, but editing does not make it sendable again; Send now still rejects a Sent record.
- Delete asks for confirmation and removes the campaign row. Its per-recipient send history is deliberately retained.
- Do not edit a sent record to represent a new promotion. Create a new campaign so content and delivery evidence stay aligned.
Send Now Safely
- Create or open the approved delivery campaign and run Preview reach again.
- Confirm the campaign is a fresh record with no dry-run logs attached.
- Select Send now. Posnic saves current form values before it asks for confirmation.
- Read the confirmation: reachable customers will be messaged. Cancel if any audience, copy or provider check is incomplete.
- Confirm only once. The campaign moves through Sending and finishes as Sent or Partial.
- Record the displayed sent, failed and skipped totals and compare them with Preview reach.
- Open the list again and verify channel, audience label, status and accumulated result totals.
Schedule a Future Send
A future campaign is sent only when the server's due-campaign runner executes. Invalid dates are rejected, but the service does not itself reject a past time. Confirm server and branch time-zone policy before scheduling.
- Enter a date and time in the field beside Schedule. The browser submits the datetime-local value and the server stores the parsed instant.
- Select Schedule. Posnic saves the campaign first, then marks it Scheduled and stores schedule_at.
- Verify the Scheduled badge in the list; the current list does not display the scheduled timestamp, so retain the approved time separately.
- Keep the branch provider connected until the scheduled runner processes the due campaign.
- After the due time, verify the status and result totals rather than assuming the runner dispatched it.
Read Status and Result Totals
| Value | Meaning | Operator response |
|---|---|---|
| Draft | Saved but not scheduled or successfully sent. | Review and preview; do not assume delivery. |
| Scheduled | Waiting for the due runner at schedule_at. | Verify timing and provider readiness. |
| Sending | A real send has started. | Do not click Send again. |
| Sent | The run finished with no provider failures, including a run where everyone was skipped. | Read totals; Sent does not guarantee at least one recipient received a message. |
| Partial | One or more provider sends failed, whether or not another send succeeded. | Fix the provider issue, then use the controlled retry behavior. |
| Result | Cumulative sent and failed counters; skipped appears in backend result but the list summarizes sent and failed only. | Keep the immediate result alert when skipped totals matter. |
Retry a Partial Campaign
A Partial campaign can be sent again. Posnic suppresses every customer with an earlier non-failed recipient record and retries only customers whose prior record was Failed. This makes successful deliveries idempotent per campaign and customer.
- Fix the SMS or WhatsApp provider before retrying.
- Do not change the audience or message before a retry; doing so makes the original and retry evidence describe different content.
- Customers skipped for no phone, opt-out or dry run are treated as processed and are not reconsidered on that same campaign.
- Successful retry increments the cumulative Sent count, while the earlier Failed count remains in the campaign totals.
- A fully Sent or currently Sending campaign is rejected by Send now.
Permissions, Scope and Privacy
| Area | Current behavior | Operating rule |
|---|---|---|
| Read and preview | Authenticated campaign routes list records and preview audiences without an explicit Branch Write check. | Limit Marketing access to trusted staff. |
| Create, update, delete, schedule, real send | Rejected only when Branch Write is explicitly false. | Use a manager-controlled role and approval process. |
| Dry run | Does not require Branch Write in the campaign controller, but it writes recipient logs. | Treat it as an auditable data action, not a harmless screen preview. |
| Campaign records | List, get and audience selection are license-wide; branch_id is metadata and chooses the messaging provider. | Review cross-outlet reach and customer consent before every send. |
| Recipient data | Logs contain customer ID, name, phone, rendered message, channel, outcome and error. | Handle campaign history as customer personal data. |
| Opt-out | Only an explicit false blocks the selected channel; SMS and WhatsApp preferences are separate. | Maintain consent on the customer record and follow applicable messaging law. |
Controlled Campaign Test Matrix
| Test | Expected result | Evidence |
|---|---|---|
| Preview only | No campaign saved and no message dispatched. | Reach total and unchanged campaign list. |
| Missing phone | Customer counts in total but not reachable and is skipped in a run. | Customer record and skipped result. |
| Channel opt-out | Excluded for that channel but may remain reachable on the other channel. | Both channel previews. |
| Tier boundary | Only exact current tier matches. | Loyalty profile and preview sample. |
| Points boundary | A customer exactly at the threshold is included. | Points balance and reach count. |
| Lapsed | Old, null and absent purchase dates are included. | Customer histories and reach count. |
| Merge rendering | Known fields render current values; unknown token stays literal. | Temporary dry-run recipient log. |
| Dry-run isolation | Zero adapter calls and campaign status unchanged. | Dry-run result and no received message. |
| Real send | Reachable opted-in phone receives once. | Provider ID/log, phone receipt and campaign totals. |
| Provider failure | Campaign becomes Partial and the recipient is Failed. | Error and failed count. |
| Partial retry | Only previously failed recipients are attempted. | Provider logs before and after retry. |
| Scheduled future | No send before due time; runner sends after due. | Scheduled badge and later provider log. |
Troubleshoot Campaigns
| Problem | Likely cause | Action |
|---|---|---|
| Campaigns tab is missing | Marketing is disabled or navigation access is restricted. | Enable Marketing with an authorized role and reopen Manage. |
| Reach is zero | No matching customers, no phones or all matching customers opted out. | Check total versus reachable, then repair source customer data. |
| Tier list is empty | Loyalty tiers did not load or are not configured. | Configure Loyalty and reopen Campaigns. |
| Category reaches everyone | Blank or incorrect internal category ID fell back to the broad query. | Do not send; obtain and verify the exact category ID, then preview again. |
| Dry run shows 0 sent | Dry-run rows are counted as skipped in the returned summary. | Confirm no messages arrived and inspect the dry-run audit result. |
| Real send after dry run reaches nobody | The same campaign's dry-run records triggered de-duplication. | Create a fresh approved delivery campaign; never delete customer consent or recipient history to bypass it. |
| Status is Sent but sent count is zero | The audience was empty or all recipients were skipped without provider failures. | Read the immediate totals and investigate reach before claiming delivery. |
| Status is Partial | At least one provider dispatch failed. | Fix provider/configuration, preserve evidence and retry only the unchanged Partial campaign. |
| Scheduled campaign did not send | Due runner has not executed, provider is unavailable or time interpretation differed. | Check stored time, server scheduler and provider logs. |
| Send says already sent | Campaign is Sent or still Sending. | Do not bypass the guard; create a new campaign for new content or audience. |
| Wrong names or loyalty values | Source customer or loyalty data was blank/stale at render time. | Correct source records and create a new campaign when content must change. |