backend-manager 5.0.202 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/CLAUDE.md +43 -1501
- package/docs/admin-post-route.md +24 -0
- package/docs/ai-library.md +23 -0
- package/docs/architecture.md +31 -0
- package/docs/auth-hooks.md +74 -0
- package/docs/cli-firestore-auth.md +59 -0
- package/docs/cli-logs.md +67 -0
- package/docs/code-patterns.md +67 -0
- package/docs/common-operations.md +64 -0
- package/docs/directory-structure.md +119 -0
- package/docs/environment-detection.md +7 -0
- package/docs/file-naming.md +11 -0
- package/docs/marketing-campaigns.md +244 -0
- package/docs/marketing-fields.md +25 -0
- package/docs/mcp.md +95 -0
- package/docs/payment-system.md +325 -0
- package/docs/response-headers.md +7 -0
- package/docs/routes.md +126 -0
- package/docs/sanitization.md +61 -0
- package/docs/schemas.md +39 -0
- package/docs/stripe-webhook-forwarding.md +18 -0
- package/docs/testing.md +129 -0
- package/docs/usage-rate-limiting.md +67 -0
- package/package.json +8 -4
- package/src/defaults/CHANGELOG.md +15 -0
- package/src/defaults/CLAUDE.md +8 -4
- package/src/defaults/docs/README.md +17 -0
- package/src/defaults/test/README.md +33 -0
- package/src/manager/events/cron/daily/marketing-newsletter-generate.js +48 -8
- package/src/manager/functions/core/actions/api/admin/create-post.js +3 -27
- package/src/manager/helpers/settings.js +26 -7
- package/src/manager/helpers/utilities.js +21 -0
- package/src/manager/index.js +1 -1
- package/src/manager/libraries/ai/index.js +162 -0
- package/src/manager/libraries/ai/providers/anthropic.js +193 -0
- package/src/manager/libraries/ai/providers/claude-code.js +206 -0
- package/src/manager/libraries/ai/providers/openai.js +934 -0
- package/src/manager/libraries/disposable-domains.json +2 -0
- package/src/manager/libraries/email/generators/lib/filter.js +179 -0
- package/src/manager/libraries/email/generators/lib/image-host.js +231 -0
- package/src/manager/libraries/email/generators/lib/mjml-template.js +83 -0
- package/src/manager/libraries/email/generators/lib/structure.js +278 -0
- package/src/manager/libraries/email/generators/lib/svg-illustrator.js +184 -0
- package/src/manager/libraries/email/generators/lib/templates/classic-schema.js +63 -0
- package/src/manager/libraries/email/generators/lib/templates/clean.js +82 -0
- package/src/manager/libraries/email/generators/lib/templates/editorial-helpers.js +100 -0
- package/src/manager/libraries/email/generators/lib/templates/editorial.js +317 -0
- package/src/manager/libraries/email/generators/lib/templates/field-report-helpers.js +138 -0
- package/src/manager/libraries/email/generators/lib/templates/field-report.js +497 -0
- package/src/manager/libraries/email/generators/lib/templates/index.js +28 -0
- package/src/manager/libraries/email/generators/lib/templates/shared.js +534 -0
- package/src/manager/libraries/email/generators/newsletter.js +377 -95
- package/src/manager/libraries/email/marketing/index.js +5 -2
- package/src/manager/libraries/email/providers/beehiiv.js +7 -3
- package/src/manager/libraries/openai.js +13 -932
- package/src/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
- package/src/manager/routes/admin/post/post.js +10 -17
- package/templates/_.env +4 -0
- package/templates/_.gitignore +1 -0
- package/templates/backend-manager-config.json +48 -4
- package/test/helpers/slugify.js +394 -0
- package/test/marketing/fixtures/clean.json +31 -0
- package/test/marketing/fixtures/editorial.json +31 -0
- package/test/marketing/fixtures/field-report.json +54 -0
- package/test/marketing/newsletter-generate.js +731 -0
- package/test/marketing/newsletter-templates.js +512 -0
- package/test/routes/admin/deduplicate-image-alts.js +190 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Marketing Campaign System
|
|
2
|
+
|
|
3
|
+
## Campaign CRUD Routes (admin-only)
|
|
4
|
+
|
|
5
|
+
| Method | Endpoint | Purpose |
|
|
6
|
+
|--------|----------|---------|
|
|
7
|
+
| POST | `/marketing/campaign` | Create campaign (immediate or scheduled) |
|
|
8
|
+
| GET | `/marketing/campaign` | List/filter campaigns by date range, status, type |
|
|
9
|
+
| PUT | `/marketing/campaign` | Update pending campaigns (reschedule, edit) |
|
|
10
|
+
| DELETE | `/marketing/campaign` | Delete pending campaigns |
|
|
11
|
+
|
|
12
|
+
## Firestore Collection: `marketing-campaigns/{id}`
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
{
|
|
16
|
+
settings: { name, subject, preheader, content, template, sender, segments, excludeSegments, ... },
|
|
17
|
+
sendAt: 1743465600, // Unix timestamp (any format accepted, normalized on create)
|
|
18
|
+
status: 'pending', // pending | sent | failed
|
|
19
|
+
type: 'email', // email | push
|
|
20
|
+
recurrence: { pattern, hour, day }, // Optional — makes it recurring
|
|
21
|
+
generator: 'newsletter', // Optional — runs content generator before sending
|
|
22
|
+
recurringId: '_recurring-sale', // Present on history docs (links to parent template)
|
|
23
|
+
generatedFrom: '_recurring-newsletter', // Present on generated docs
|
|
24
|
+
results: { sendgrid: {...}, beehiiv: {...} },
|
|
25
|
+
metadata: { created: {...}, updated: {...} },
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Campaign Types
|
|
30
|
+
|
|
31
|
+
- **Email**: dispatches to SendGrid (Single Send) + Beehiiv (Post) via `mailer.sendCampaign()`
|
|
32
|
+
- **Push**: dispatches to FCM via `notification.send()` (shared library)
|
|
33
|
+
- Content is **markdown** — converted to HTML at send time. Template variables resolved before conversion.
|
|
34
|
+
|
|
35
|
+
## Recurring Campaigns
|
|
36
|
+
|
|
37
|
+
Campaigns with a `recurrence` field repeat automatically:
|
|
38
|
+
- Cron fires → creates a **history doc** (same collection, `recurringId` set) → advances `sendAt` to next occurrence
|
|
39
|
+
- Status stays `pending` on the recurring template, history docs are `sent`/`failed`
|
|
40
|
+
- `_` prefix on IDs groups them at top of Firestore console
|
|
41
|
+
|
|
42
|
+
Recurrence patterns: `daily`, `weekly`, `monthly`, `quarterly`, `yearly`
|
|
43
|
+
|
|
44
|
+
## Generator Campaigns
|
|
45
|
+
|
|
46
|
+
Campaigns with a `generator` field don't send directly. A daily cron pre-generates content 24 hours before `sendAt`:
|
|
47
|
+
1. Daily cron finds generator campaigns due within 24 hours
|
|
48
|
+
2. Runs the generator module (e.g., `generators/newsletter.js`)
|
|
49
|
+
3. Creates a NEW standalone `pending` campaign with generated content
|
|
50
|
+
4. Advances the recurring template's `sendAt`
|
|
51
|
+
5. Generated campaign appears on calendar for review, sent by frequent cron when due
|
|
52
|
+
|
|
53
|
+
## Template Variables
|
|
54
|
+
|
|
55
|
+
Resolved at send time via `powertools.template()`. Single braces `{var}` for campaign-level, double `{{var}}` for SendGrid template-level.
|
|
56
|
+
|
|
57
|
+
| Variable | Example Output |
|
|
58
|
+
|----------|---------------|
|
|
59
|
+
| `{brand.name}` | Somiibo |
|
|
60
|
+
| `{brand.id}` | somiibo |
|
|
61
|
+
| `{brand.url}` | https://somiibo.com |
|
|
62
|
+
| `{season.name}` | Winter, Spring, Summer, Fall |
|
|
63
|
+
| `{holiday.name}` | Black Friday, Christmas, Valentine's Day, etc. |
|
|
64
|
+
| `{date.month}` | November |
|
|
65
|
+
| `{date.year}` | 2026 |
|
|
66
|
+
| `{date.full}` | March 17, 2026 |
|
|
67
|
+
|
|
68
|
+
## UTM Auto-Tagging
|
|
69
|
+
|
|
70
|
+
`libraries/email/utm.js` scans HTML for `<a href>` matching the brand's domain and appends UTM params. Applied to both marketing campaigns and transactional emails.
|
|
71
|
+
|
|
72
|
+
Defaults: `utm_source=brand.id`, `utm_medium=email`, `utm_campaign=name`, `utm_content=type`. Override via `settings.utm` object.
|
|
73
|
+
|
|
74
|
+
## Segments SSOT
|
|
75
|
+
|
|
76
|
+
`SEGMENTS` dictionary in `constants.js` — 22 segment definitions. OMEGA creates them in SendGrid, BEM resolves keys to provider IDs at runtime via `resolveSegmentIds()` (cached).
|
|
77
|
+
|
|
78
|
+
| Category | Segments |
|
|
79
|
+
|----------|----------|
|
|
80
|
+
| Subscription (9) | `subscription_free`, `subscription_paid`, `subscription_trialing`, `subscription_cancelling`, `subscription_suspended`, `subscription_cancelled`, `subscription_churned`, `subscription_ever_paid`, `subscription_never_paid` |
|
|
81
|
+
| Lifecycle (5) | `lifecycle_7d`, `lifecycle_30d`, `lifecycle_90d`, `lifecycle_6m`, `lifecycle_1y` |
|
|
82
|
+
| Engagement (5) | `engagement_active_30d`, `engagement_active_90d`, `engagement_inactive_90d`, `engagement_inactive_5m`, `engagement_inactive_6m` |
|
|
83
|
+
|
|
84
|
+
Campaigns reference segments by SSOT key: `segments: ['subscription_free']`. Auto-translated to provider IDs.
|
|
85
|
+
|
|
86
|
+
## Contact Pruning
|
|
87
|
+
|
|
88
|
+
`cron/daily/marketing-prune.js` — runs 1st of each month. Two stages:
|
|
89
|
+
1. **Re-engagement**: send email to `engagement_inactive_5m` (excluding `engagement_inactive_6m`)
|
|
90
|
+
2. **Prune**: export `engagement_inactive_6m` contacts, bulk delete from SendGrid + Beehiiv. Never prunes paying customers.
|
|
91
|
+
|
|
92
|
+
## Newsletter Generator
|
|
93
|
+
|
|
94
|
+
`generators/newsletter.js` orchestrates a multi-step pipeline that produces a fully rendered, email-safe newsletter. Output is HTML (not markdown) — the marketing library detects `settings.contentHtml` and uses it directly, skipping the markdown pipeline.
|
|
95
|
+
|
|
96
|
+
Pipeline:
|
|
97
|
+
1. Fetch sources: `GET {parentUrl}/newsletter/sources?category=X&claimFor=brandId` (atomic claim)
|
|
98
|
+
2. **structure.js** — Generic dispatcher. Resolves the active template, merges `BASE_SCHEMA` (universal fields: subject, preheader, signoff, citations) with the template's own `schema` fragment, calls the template's `buildPrompt({brand, newsletterConfig, sources})` to get the AI brief, runs the AI call, and normalizes the result via the template's optional `normalize()`. Default provider: `openai` (override per-run only via `NEWSLETTER_PROVIDER_STRUCTURE` env).
|
|
99
|
+
3. **svg-illustrator.js** — One SVG per section in parallel (`Promise.all`). Iterates `structure.sections` — templates whose content shape isn't section-based (e.g. field-report uses `dispatches`) populate `sections` in their `normalize()` step so this loop keeps working unchanged. Default provider: `anthropic` (override via `NEWSLETTER_PROVIDER_SVG` env).
|
|
100
|
+
4. **mjml-template.js** — Resolves the template by name from `templates/index.js`, calls `template.build({structure, imagePaths, theme, ...})` for the MJML, compiles to email-safe HTML via the `mjml` package. Brand-domain links get UTM-tagged via the existing `tagLinks()` utility.
|
|
101
|
+
5. Mark used: `PUT {parentUrl}/newsletter/sources` per source
|
|
102
|
+
|
|
103
|
+
## Template-owned schemas
|
|
104
|
+
|
|
105
|
+
Each template under `lib/templates/` owns its own content shape. Templates export:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
module.exports = {
|
|
109
|
+
build({ structure, imagePaths, theme, brandName, brandUrl, brandAddress, sponsorships, now }), // → MJML
|
|
110
|
+
meta: { name, description, requires, optional, supports },
|
|
111
|
+
schema: { required, properties }, // JSON schema FRAGMENT — merged into BASE_SCHEMA
|
|
112
|
+
normalize(structure, { brand, newsletterConfig }), // optional post-AI normalization
|
|
113
|
+
buildPrompt({ brand, newsletterConfig, sources }), // optional — defaults to classic prompt
|
|
114
|
+
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`BASE_SCHEMA` (in `structure.js`) declares the universals every newsletter must have: `subject`, `preheader`, `signoff`, `citations`. Templates merge their own fields on top via `schema.properties` + `schema.required`.
|
|
118
|
+
|
|
119
|
+
**Adding a new template:**
|
|
120
|
+
1. Create `lib/templates/<name>.js` with `build`, `meta`, `schema`, and `normalize`. Add `buildPrompt` if the content shape requires a custom AI brief.
|
|
121
|
+
2. Register it in `lib/templates/index.js`.
|
|
122
|
+
3. **Add a matching fixture** at `test/marketing/fixtures/<name>.json` with the same content shape. This is REQUIRED — the iteration test loads it by default when the active brand's template is your new one. Without it, the default test run fails with "fixture not found".
|
|
123
|
+
4. Add the template name to the `TEMPLATES` array in `test/marketing/newsletter-templates.js` so the fixture suite renders it. Add per-template assertions if the new template renders unique identity markers (e.g. field-report's `LEAD DISPATCH` kicker).
|
|
124
|
+
5. Audit graceful omission — every template's `build()` must handle missing optional fields (return `''` for omitted blocks rather than throwing). Existing templates (clean, editorial, field-report) all do this.
|
|
125
|
+
|
|
126
|
+
Existing classic-shape templates (`clean`, `editorial`) share their schema via `lib/templates/classic-schema.js`. New templates with the same `{intro, sections: [{title, body, cta, image_prompt}]}` shape should reuse `CLASSIC_SCHEMA` + `normalizeClassic` rather than duplicating.
|
|
127
|
+
|
|
128
|
+
## Iteration test default behavior
|
|
129
|
+
|
|
130
|
+
`test/marketing/newsletter-generate.js` runs in **fixture mode by default** — it loads `test/marketing/fixtures/<active-template>.json` and renders straight through MJML. ~25-50ms, no AI, $0. This is what runs in CI and what you use for layout iteration.
|
|
131
|
+
|
|
132
|
+
Set `TEST_EXTENDED_MODE=1` to switch to the full AI pipeline against real sources from the parent server. That mode requires `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `BACKEND_MANAGER_KEY`, and a parent URL.
|
|
133
|
+
|
|
134
|
+
Per-brand customization lives under `marketing.beehiiv.content` — nested under `beehiiv` because Beehiiv is the platform that publishes the result, and the whole pipeline is gated by `marketing.beehiiv.enabled`. There's no separate enabled flag on the content block (redundant — disabling beehiiv disables content generation as a side effect, since there's nowhere for the generated content to land). The `content` block name is provider-agnostic on purpose — eventually `marketing.sendgrid.content` would describe a similarly-shaped pipeline for promo email blasts:
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
marketing.beehiiv = {
|
|
138
|
+
enabled: true,
|
|
139
|
+
publicationId: 'pub_xxxxx',
|
|
140
|
+
content: {
|
|
141
|
+
categories: ['social-media', 'marketing'],
|
|
142
|
+
instructions: '...', // free-form text for the AI
|
|
143
|
+
tone: 'professional', // 'casual' | 'actionable' | 'witty' | etc.
|
|
144
|
+
template: 'field-report', // clean | editorial | field-report
|
|
145
|
+
theme: { primaryColor, secondaryColor, accentColor, font },
|
|
146
|
+
sponsorships: [ ... ],
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
AI provider defaults live in code (openai for structure, anthropic for SVG — each chosen for what each model does best). Override per-run only via env: `NEWSLETTER_PROVIDER_STRUCTURE` / `NEWSLETTER_PROVIDER_SVG`. No per-brand override — every brand uses the same defaults.
|
|
152
|
+
|
|
153
|
+
## Asset hosting (production cron flow)
|
|
154
|
+
|
|
155
|
+
The daily cron uploads PNGs + the rendered `newsletter.html` to the public `itw-creative-works/newsletter-assets` repo as two atomic Git Trees commits per issue. Folder layout: `{brandId}/{campaignId}/section-N.png` + `{brandId}/{campaignId}/newsletter.html`.
|
|
156
|
+
|
|
157
|
+
The `campaignId` is the same Firestore doc ID the cron uses for the generated `marketing-campaigns/{newId}` doc, reserved up front so the GitHub URLs and the Firestore doc always match.
|
|
158
|
+
|
|
159
|
+
Asset URLs are stamped onto the generated campaign doc:
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
marketing-campaigns/{newId}: {
|
|
163
|
+
settings: { subject, preheader, contentHtml, ... },
|
|
164
|
+
assets: {
|
|
165
|
+
campaignId, // same as the doc id
|
|
166
|
+
folderUrl, // https://github.com/itw-creative-works/newsletter-assets/tree/main/{brandId}/{campaignId}
|
|
167
|
+
htmlUrl, // https://raw.githubusercontent.com/.../newsletter.html — paste this into Beehiiv
|
|
168
|
+
imageUrls: [...] // raw.githubusercontent.com URLs already embedded in contentHtml
|
|
169
|
+
},
|
|
170
|
+
meta: { tokens, cost, durations, source scores },
|
|
171
|
+
...
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Requires `GITHUB_TOKEN` env var (org-scoped, write access to `newsletter-assets`). Without it, the cron's HTML/image upload calls throw and the run aborts.
|
|
176
|
+
|
|
177
|
+
## Iteration test asset story
|
|
178
|
+
|
|
179
|
+
`NEWSLETTER_GITHUB_UPLOAD=1` in the iteration test enables the same upload flow against the same repo. Local-only by default for fast layout iteration (writes to `.temp/newsletter/run-<stamp>/newsletter.html`).
|
|
180
|
+
|
|
181
|
+
| Module | Purpose |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `lib/structure.js` | Generic AI dispatcher — merges template schema with BASE_SCHEMA, calls template.buildPrompt, normalizes |
|
|
184
|
+
| `lib/svg-illustrator.js` | Per-section SVG → PNG (rasterized via `@resvg/resvg-js`) |
|
|
185
|
+
| `lib/mjml-template.js` | Template dispatcher → MJML → email-safe HTML, UTM-tagged |
|
|
186
|
+
| `lib/templates/index.js` | Template registry (`clean`, `editorial`, `field-report`) |
|
|
187
|
+
| `lib/templates/classic-schema.js` | Shared content schema for `clean` + `editorial` |
|
|
188
|
+
| `lib/templates/shared.js` | Opinionated `shell()` + primitives (sponsorship, citations, footer, address) |
|
|
189
|
+
| `lib/templates/editorial-helpers.js` | Editorial-only helpers (pullquote, issue number, eyebrow) |
|
|
190
|
+
| `lib/templates/field-report-helpers.js` | Field-report-only helpers (kicker, dispatch dateline, terminal block, terminator) |
|
|
191
|
+
| `newsletter.js` | Orchestrator — calls lib modules, fetches sources, claims sources |
|
|
192
|
+
| `test/marketing/fixtures/{name}.json` | Hand-crafted structure per template (loaded by iteration test in fixture mode) |
|
|
193
|
+
|
|
194
|
+
## Seed Campaigns
|
|
195
|
+
|
|
196
|
+
Created by `npx mgr setup` (idempotent, enforced fields checked every run):
|
|
197
|
+
|
|
198
|
+
| ID | Type | Description |
|
|
199
|
+
|----|------|-------------|
|
|
200
|
+
| `_recurring-sale` | email (sendgrid) | Seasonal sale targeting free + cancelled + churned users |
|
|
201
|
+
| `_recurring-newsletter` | email (beehiiv) | AI-generated newsletter from parent server sources |
|
|
202
|
+
|
|
203
|
+
## Marketing Config
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
marketing: {
|
|
207
|
+
sendgrid: { enabled: true },
|
|
208
|
+
beehiiv: {
|
|
209
|
+
enabled: false,
|
|
210
|
+
publicationId: 'pub_xxxxx',
|
|
211
|
+
content: {
|
|
212
|
+
categories: ['social-media', 'marketing'],
|
|
213
|
+
instructions: '', // free-form AI instructions
|
|
214
|
+
tone: 'professional',
|
|
215
|
+
template: 'clean', // clean | editorial | field-report
|
|
216
|
+
theme: { primaryColor, secondaryColor, accentColor, font },
|
|
217
|
+
sponsorships: [ ... ],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
prune: { enabled: true },
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Key Marketing Files
|
|
225
|
+
|
|
226
|
+
| Purpose | File |
|
|
227
|
+
|---------|------|
|
|
228
|
+
| Marketing library | `src/manager/libraries/email/marketing/index.js` |
|
|
229
|
+
| Field + segment SSOT | `src/manager/libraries/email/constants.js` |
|
|
230
|
+
| UTM tagging | `src/manager/libraries/email/utm.js` |
|
|
231
|
+
| Newsletter generator | `src/manager/libraries/email/generators/newsletter.js` |
|
|
232
|
+
| Newsletter copy (AI) | `src/manager/libraries/email/generators/lib/structure.js` |
|
|
233
|
+
| Newsletter SVG (AI) | `src/manager/libraries/email/generators/lib/svg-illustrator.js` |
|
|
234
|
+
| Newsletter MJML → HTML | `src/manager/libraries/email/generators/lib/mjml-template.js` |
|
|
235
|
+
| Newsletter asset host (GitHub upload — PNGs + newsletter.html) | `src/manager/libraries/email/generators/lib/image-host.js` |
|
|
236
|
+
| Unified AI library | `src/manager/libraries/ai/index.js` (OpenAI + Anthropic via `Manager.AI(assistant).request({ provider, ... })`) |
|
|
237
|
+
| Notification library | `src/manager/libraries/notification.js` |
|
|
238
|
+
| SendGrid provider | `src/manager/libraries/email/providers/sendgrid.js` |
|
|
239
|
+
| Beehiiv provider | `src/manager/libraries/email/providers/beehiiv.js` |
|
|
240
|
+
| Campaign routes | `src/manager/routes/marketing/campaign/{get,post,put,delete}.js` |
|
|
241
|
+
| Campaign cron | `src/manager/cron/frequent/marketing-campaigns.js` |
|
|
242
|
+
| Newsletter pre-gen cron | `src/manager/cron/daily/marketing-newsletter-generate.js` |
|
|
243
|
+
| Pruning cron | `src/manager/cron/daily/marketing-prune.js` |
|
|
244
|
+
| Seed campaigns | `src/cli/commands/setup-tests/helpers/seed-campaigns.js` |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Marketing Custom Fields
|
|
2
|
+
|
|
3
|
+
BEM syncs user data to marketing providers (SendGrid, Beehiiv) as custom fields. Field definitions live in a single dictionary; OMEGA provisions them in each provider.
|
|
4
|
+
|
|
5
|
+
## Adding a New Field
|
|
6
|
+
|
|
7
|
+
1. Add the field to `FIELDS` in `src/manager/libraries/email/constants.js` — the key IS the field name in both providers. Set `source`, `path`, `type`.
|
|
8
|
+
2. Add matching entry in OMEGA's `src/lib/bem-fields.js` with `name`, `display`, `type`. If Beehiiv has it built-in (e.g., country, utm_source), set `beehiivBuiltIn: true`.
|
|
9
|
+
3. Run OMEGA: `npm start -- --service=sendgrid,beehiiv --brand=X`
|
|
10
|
+
4. BEM resolves field IDs at runtime — no provider code changes needed.
|
|
11
|
+
|
|
12
|
+
## How It Works
|
|
13
|
+
|
|
14
|
+
- **SendGrid**: `resolveFieldIds()` fetches field definitions from the SendGrid API, builds a name-to-ID cache, and maps values to SendGrid's auto-generated IDs (e.g., `brand_id` maps to `e35_T`).
|
|
15
|
+
- **Beehiiv**: BEM uses the key directly as the custom field name — no ID resolution needed.
|
|
16
|
+
- **OMEGA**: The `ensure/custom-fields.js` handlers are idempotent — they fetch existing fields and only create what is missing.
|
|
17
|
+
|
|
18
|
+
## Key Files
|
|
19
|
+
|
|
20
|
+
| Purpose | File |
|
|
21
|
+
|---------|------|
|
|
22
|
+
| Field dictionary (BEM SSOT) | `src/manager/libraries/email/constants.js` |
|
|
23
|
+
| Field provisioning list (OMEGA SSOT) | `omega-manager/src/lib/bem-fields.js` |
|
|
24
|
+
| SendGrid provisioning | `omega-manager/src/services/sendgrid/ensure/custom-fields.js` |
|
|
25
|
+
| Beehiiv provisioning | `omega-manager/src/services/beehiiv/ensure/custom-fields.js` |
|
package/docs/mcp.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Model Context Protocol (MCP)
|
|
2
|
+
|
|
3
|
+
BEM includes a built-in MCP server that exposes BEM routes as tools for Claude Chat, Claude Code, and other MCP clients.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
Two transport modes:
|
|
8
|
+
- **Stdio** (local): `npx mgr mcp` — for Claude Code / Claude Desktop
|
|
9
|
+
- **Streamable HTTP** (remote): `POST /backend-manager/mcp` — for Claude Chat (stateless, Firebase Functions compatible)
|
|
10
|
+
|
|
11
|
+
## Available Tools (19)
|
|
12
|
+
|
|
13
|
+
| Tool | Route | Description |
|
|
14
|
+
|------|-------|-------------|
|
|
15
|
+
| `firestore_read` | `GET /admin/firestore` | Read a Firestore document by path |
|
|
16
|
+
| `firestore_write` | `POST /admin/firestore` | Write/merge a Firestore document |
|
|
17
|
+
| `firestore_query` | `POST /admin/firestore/query` | Query a collection with where/orderBy/limit |
|
|
18
|
+
| `send_email` | `POST /admin/email` | Send transactional email via SendGrid |
|
|
19
|
+
| `send_notification` | `POST /admin/notification` | Send push notification via FCM |
|
|
20
|
+
| `get_user` | `GET /user` | Get authenticated user info |
|
|
21
|
+
| `get_subscription` | `GET /user/subscription` | Get subscription info for a user |
|
|
22
|
+
| `sync_users` | `POST /admin/users/sync` | Sync user data across systems |
|
|
23
|
+
| `list_campaigns` | `GET /marketing/campaign` | List marketing campaigns |
|
|
24
|
+
| `create_campaign` | `POST /marketing/campaign` | Create a marketing campaign |
|
|
25
|
+
| `get_stats` | `GET /admin/stats` | Get system statistics |
|
|
26
|
+
| `cancel_subscription` | `POST /payments/cancel` | Cancel subscription at period end |
|
|
27
|
+
| `refund_payment` | `POST /payments/refund` | Process a refund |
|
|
28
|
+
| `run_cron` | `POST /admin/cron` | Trigger a cron job by ID |
|
|
29
|
+
| `create_post` | `POST /admin/post` | Create a blog post |
|
|
30
|
+
| `create_backup` | `POST /admin/backup` | Create a Firestore backup |
|
|
31
|
+
| `run_hook` | `POST /admin/hook` | Execute a custom hook |
|
|
32
|
+
| `generate_uuid` | `POST /general/uuid` | Generate a UUID |
|
|
33
|
+
| `health_check` | `GET /test/health` | Check server health |
|
|
34
|
+
|
|
35
|
+
## Authentication
|
|
36
|
+
|
|
37
|
+
- **Stdio (local):** Reads `BACKEND_MANAGER_KEY` from `functions/.env` automatically
|
|
38
|
+
- **HTTP (remote):** OAuth 2.1 Authorization Code flow with PKCE. Claude Chat handles the flow — user pastes BEM key once on the authorize page. If `OAuth Client ID` is set to the BEM key in the connector config, the authorize step auto-approves.
|
|
39
|
+
|
|
40
|
+
## Hosting Rewrites
|
|
41
|
+
|
|
42
|
+
The `npx mgr setup` command automatically adds required Firebase Hosting rewrites for MCP OAuth:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"source": "{/backend-manager,/backend-manager/**,/.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server,/authorize,/token}",
|
|
47
|
+
"function": "bm_api"
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## CLI Usage
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx mgr mcp # Start stdio MCP server (for Claude Code)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Claude Code Configuration
|
|
58
|
+
|
|
59
|
+
Add to `.claude/settings.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"backend-manager": {
|
|
65
|
+
"command": "npx",
|
|
66
|
+
"args": ["bm", "mcp"],
|
|
67
|
+
"cwd": "/path/to/consumer-project"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Claude Chat Configuration
|
|
74
|
+
|
|
75
|
+
1. Go to Settings → Custom Connectors → Add
|
|
76
|
+
2. **URL:** `https://api.yourdomain.com/backend-manager/mcp`
|
|
77
|
+
3. **OAuth Client ID:** your `BACKEND_MANAGER_KEY` (enables auto-approve)
|
|
78
|
+
4. **OAuth Client Secret:** your `BACKEND_MANAGER_KEY`
|
|
79
|
+
|
|
80
|
+
## Key Files
|
|
81
|
+
|
|
82
|
+
| Purpose | File |
|
|
83
|
+
|---------|------|
|
|
84
|
+
| Tool definitions | `src/mcp/tools.js` |
|
|
85
|
+
| HTTP handler (stateless + OAuth) | `src/mcp/handler.js` |
|
|
86
|
+
| Stdio server | `src/mcp/index.js` |
|
|
87
|
+
| HTTP client | `src/mcp/client.js` |
|
|
88
|
+
| CLI command | `src/cli/commands/mcp.js` |
|
|
89
|
+
| MCP route interception | `src/manager/index.js` (`_handleMcp`, `resolveMcpRoutePath`) |
|
|
90
|
+
| Hosting rewrites setup | `src/cli/commands/setup-tests/hosting-rewrites.js` |
|
|
91
|
+
|
|
92
|
+
## Adding New Tools
|
|
93
|
+
|
|
94
|
+
1. Add the tool definition to `src/mcp/tools.js` with `name`, `description`, `method`, `path`, and `inputSchema`
|
|
95
|
+
2. The tool automatically maps to the corresponding BEM route via the HTTP client — no handler code needed
|