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:
@@ -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.
|
||||
Reference in New Issue
Block a user