docs: add the campaign management guide

Setup, first campaign, daily use, and the chat prompts — including the
MCP path for adding CRM fields from a conversation.

Records the two things currently blocking a live launch: the ad account
has no payment method, which only Business Manager can fix, and the
operator token has to be set before an approval can authorise spend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
AFFAANh
2026-08-04 11:13:20 +05:30
co-authored by Claude Opus 5
parent 7317006c04
commit 5e52c425ba
+339
View File
@@ -0,0 +1,339 @@
# Campaign management: setup and operation
End-to-end guide for running Meta ad campaigns from MaskanX, with results
reported in Maskan CRM.
Everything below is written to be followed in order the first time. After
that, jump to [Daily use](#daily-use) or the
[prompt reference](#prompt-reference).
---
## What the system does
```
MaskanX Meta
┌────────────────────────────────┐ ┌────────────────────┐
│ draft → approved → synced │───────▶│ campaign (PAUSED) │
│ ↓ │ │ └ ad set (PAUSED) │
│ launch │───────▶│ └ ad (PAUSED) │
│ ↓ │ │ ↓ ACTIVE │
│ guardrails ◀── spend, leads ──│◀───────│ delivery │
└────────────┬───────────────────┘ └────────────────────┘
│ server to server (not MCP)
┌────────────────────────────────┐
│ Maskan CRM │
│ Campaigns: spend, cost/lead │
│ Leads: attributed to the ad │
│ Custom fields ◀── MCP ────────│──── "add a budget range field"
└────────────────────────────────┘
```
Two connections, deliberately different:
- **CRM data flows server to server.** Leads and campaign figures arrive on
a schedule, whether or not anyone is talking to the agent.
- **MCP changes the CRM's shape.** Asking in chat for a new field creates
it, the way HubSpot custom properties work.
---
## Part 1 — One-time setup
### 1.1 Databases
```powershell
# MaskanX
cd maskanx-backend
npm install
npm run local:migrate
npm run local:seed
# Maskan CRM
cd ..\maskan-crm-backend
npm install
npm run local:migrate
npm run local:seed
```
For running the MaskanX test suite you also need a separate database, so a
test run can never touch development data:
```sql
CREATE DATABASE campaign_test;
```
```powershell
cd maskanx-backend
npm run test:migrate
```
### 1.2 Meta credentials
In MaskanX, go to **Settings → Environments** and set:
| Key | What it is |
| --- | --- |
| `META_ADS_ACCESS_TOKEN` | System user token from Meta Business Manager |
| `META_PAGE_ID` | The Facebook Page ads are published from |
| `META_ADS_ACCOUNT_ID` | `act_...`, enables drift reconciliation |
Your current values, for reference:
- Ad account: `act_1015759234692676` (Maskan-MetaAds, INR)
- Page: `1229386810259840` (Maskan Technologies Private Limited)
**One thing is still missing on the Meta side:** the ad account has no
payment method. MaskanX will let you build, approve and sync a campaign,
but **Launch is refused** until you add one — the Marketing API cannot
create funding instruments, so it must be done in Meta Business Manager.
The Campaigns page shows a warning while this is the case.
### 1.3 Operator token
Approving and launching are the two actions that lead to money being spent,
so they have to be attributable to a named person. Set:
```
MASKANX_OPERATOR_TOKENS=farman:<some-long-random-string>
```
Then open the Campaigns page and paste the token part into **Operator**.
Without this, the operator resolves to `unauthenticated` — enough to draft
and approve, but **launching is refused**, because an approval nobody can
be named for does not authorise spend.
### 1.4 Connect the CRM
In the CRM: **Settings → Integrations → create a key**. Copy it once; it is
not shown again.
In MaskanX **Settings → Environments**:
| Key | Value |
| --- | --- |
| `MASKAN_CRM_API_URL` | `http://127.0.0.1:8091` |
| `MASKAN_CRM_INTEGRATION_KEY` | the key you just created |
### 1.5 Start everything
```powershell
cd maskan-crm-backend; npm run local # :8091
cd maskan-crm-frontend; npm run dev
cd maskanx-backend; npm run local # :8088
cd maskanx-frontend; npm run dev
```
---
## Part 2 — Your first campaign
### 2.1 Build it
Campaigns page → **New campaign**. The wizard collects objective, budget,
targeting, creative and guardrails, and shows a **live preview** of the ad
across six placements before anything exists in Meta.
Budget and guardrails are in **minor currency units** throughout: `50000`
is ₹500.00. Your account's minimum daily budget is ₹97.09.
Guardrails worth setting on the first campaign:
| Field | Meaning |
| --- | --- |
| `auto_pause_if_spend_reaches` | Pause once today's spend hits this |
| `max_campaign_spend` + `stop_loss_enabled` | Stop permanently at this total |
| `max_cost_per_lead` | Pause if leads get too expensive |
| `require_approval_for_budget_increase` | Budget cannot be raised while awaiting approval |
### 2.2 Approve, sync, launch
1. **Submit** → the campaign moves to `pending_approval`
2. **Approve** → your operator name is recorded as the approver
3. **Sync to Meta** → creates the campaign, ad set, creative and ad, **all
paused**. Nothing is spending yet.
4. **Launch** → confirms with the daily budget named, then sets the whole
chain active. **This starts spending.**
Launch is refused unless the campaign is synced, the approval names a real
operator, and the account has a payment method.
### 2.3 Watch it
- **Campaigns page** — status, sync state, spend, leads, cost per lead
- **Pause** stops delivery; **Launch** resumes it
- Within 15 minutes, guardrails begin checking; a campaign halted by one is
tagged with the rule and the observed value
- Within 2 minutes, changes made in Ads Manager are reflected back
- Within 5 minutes, leads and figures reach the CRM
---
## Part 3 — Daily use
Open the Campaigns page. The analytics panel leads with yesterday and the
last seven days: spend, leads, cost per lead, and the most and least
efficient campaign.
Both windows end **yesterday**, not today — today's figures are partial,
and including them would make every morning look like a collapse in spend.
In the CRM, **Campaigns** shows the same figures next to the leads they
produced. The leads column shows two numbers when they differ: the CRM's
own count, and Meta's attribution behind it. They diverge routinely — Meta
attributes late, and leads get entered by hand — and the gap is worth
seeing.
---
## Prompt reference
Prompts for the MaskanX chat. The agent reaches Meta and the CRM through
MCP.
### Checking things
```
List my Meta ad accounts.
```
```
Show me the balance and spend on my Meta ad account.
```
```
Check whether the Maskan CRM connection is working.
```
```
Which campaigns are live right now, and what has each spent today?
```
### Reporting
```
What did we spend yesterday, and how many leads did it bring?
```
```
Which campaign has the lowest cost per lead over the last 7 days?
```
```
Break down last month's spend for the Q3 lead gen campaign by age and
gender.
```
### Adding CRM fields — the MCP path
This is the part that works like HubSpot. Ask in chat, and the field exists
in the CRM immediately, on records and in **Custom fields**.
```
What custom fields does the CRM have on contacts?
```
```
Add a "Budget range" field to CRM contacts, with the options 50-70 lakh,
70-90 lakh, and 1 crore+.
```
```
Add a "Site visit done" yes/no field to CRM leads.
```
```
Add a "Possession date" date field to CRM leads.
```
The agent checks what already exists before adding, so it reuses a field
rather than creating a near-duplicate under a slightly different name.
Two things cannot be changed after creation: the field's **key** and its
**type**. Renaming the key would orphan every value already stored under
it; changing the type would leave values that no longer satisfy it. The
label can be changed freely.
### Campaign actions
Money-moving actions are deliberately **not** exposed as one-shot chat
commands — launching goes through the confirmation on the Campaigns page,
which names the daily budget. Ask the agent to prepare, and confirm in the
UI:
```
Draft a lead-gen campaign for 3BHK flats in Kochi, ₹800/day, targeting
25-45 year olds in Kerala. Set a stop loss at ₹20,000 total and pause it
if cost per lead goes above ₹1,500.
```
---
## Configuration reference
All optional; sensible defaults apply.
| Variable | Default | Effect |
| --- | --- | --- |
| `MASKANX_OPERATOR_TOKENS` | unset | `name:token` pairs. Unset means launch is refused. |
| `MASKANX_CAMPAIGN_RECONCILE_SECONDS` | 120 | Drift reconciliation with Meta. `0` disables. |
| `MASKANX_GUARDRAIL_INTERVAL_SECONDS` | 900 | Guardrail checks. `0` disables — logged as a warning. |
| `MASKANX_INSIGHTS_SYNC_SECONDS` | 3600 | Insight refresh. `0` disables. |
| `MASKANX_INSIGHTS_WINDOW_DAYS` | 28 | Trailing days re-fetched. Matches Meta's attribution window. |
| `MASKANX_CRM_SYNC_SECONDS` | 300 | CRM push. `0` disables. |
| `MASKANX_CRM_LEAD_WINDOW_DAYS` | 7 | How far back a never-synced campaign reads leads. |
---
## What protects you from spending money by accident
Worth knowing, because the design gives up some convenience for it:
1. **Every Meta object is created paused.** Enforced in three places: the
typed create helpers, the HTTP layer itself, and launch being the only
caller allowed to send `ACTIVE`.
2. **Launch is the only action that starts spend**, and it is the only one
behind a confirmation, which names the daily budget.
3. **An unattributable approval does not authorise spend.**
4. **Guardrails are deterministic**, not an LLM cron job — asking a
language model whether a budget has been exceeded would make an
arithmetic guarantee probabilistic.
5. **Meta is always changed before the local record.** A campaign recorded
as paused but still delivering is the outcome the whole design avoids.
6. **Deleting a campaign deletes it on Meta first.** If that fails, the
local row is kept — a campaign forgotten here but still live there would
keep spending with nothing to show it exists.
---
## Verifying it end to end
The unit suite runs entirely against a fake transport: it proves the code
sends what we think it sends, not that Meta accepts it. One opt-in test
closes that gap by syncing a real campaign, reading back from Graph that
all three objects are `PAUSED`, then deleting them:
```powershell
cd maskanx-backend
$env:MASKANX_LIVE_TESTS = "1"
$env:MASKANX_LIVE_AD_ACCOUNT_ID = "act_1015759234692676"
npm run test -- tests/test_campaign_sync_live.py
```
It creates and deletes real objects. They stay paused for their whole life,
so it cannot spend money, and cleanup runs in a `finally` block.
Routine checks:
```powershell
cd maskanx-backend; npm run test; npm run lint
cd maskanx-frontend; npm run build; npm run lint
cd maskan-crm-backend; npm run test
cd maskan-crm-frontend; npm run build; npm run lint
```
If the MaskanX suite reports more than 13 skips, `campaign_test` is
probably not reachable and the database tests are being skipped silently —
check the skip count, not just the exit code.