Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
22 KiB
Marketing Suite — architecture design (Descrybe v2)
Status: Design only — no implementation in this doc.
Date: 2026-08-04
Repos: f:/laragon/www/_MY/descrybe-v2
Inputs: PRICING-AND-USER-GROWTH.md (sibling), process-and-sell-summary.md, features.md, existing internal/billing, internal/woocommerce, internal/processing, internal/mail, Settings + Nav.
0. North star
Descrybe’s moat remains feeds → map → process → export/Woo. Marketing add-ons sit on top of a live catalog + Woo connection, not as a second product that competes with “paste ChatGPT into a plugin.”
[ Catalog + Brand kit ]
│
├── Product AI copy (existing pipeline) ── credits / BYOK
│
└── Marketing Suite
├── Audience (orders + opt-in)
├── Campaigns (seasonal / category / product)
├── Reviews (sync + social proof in copy)
└── SEO recommendations (catalog-derived)
Principle: Defaults get a merchant from “Black Friday email for buyers of Category X” in minutes. Advanced (custom prompts, BYOK, segment builders) stays behind optional panels.
1. Context from today’s codebase
| Area | Today (v2) | Implication for Marketing |
|---|---|---|
| Woo | Company-level woocommerce_configs; outbound product push only (internal/woocommerce); worker claims sync jobs |
Must add inbound order (+ later review) sync; keep credentials encrypted; reuse same store config |
| Credits | credit_balances + AssertCanStartProcessing / ConsumeCredits(..., featureName) |
Extend with feature names: campaign_copy, seo_recommend, brand_kit_ai; same wallet |
| Plans | plans / company_plans + SKU caps; public Free/Starter/Growth/Business/Enterprise in pricing-data.ts |
Need feature flags per plan (not only meters) for Marketing |
| AI | Worker Completer (OpenAI or heuristic); no tenant BYOK yet |
Campaign/SEO/Brand AI share Completer + future BYOK seam |
Platform SMTP for invites (internal/mail); noop if disabled |
Campaign send ≠ platform invite SMTP — tenant-verified provider required | |
| Nav / Settings | Nav: PIM core + WooCommerce extra; Settings tabs: profile / company / api-keys / team | Add Marketing nav group; Settings → Integrations tab (Woo, Email, Reviews, AI keys) |
| Jobs | DB claim + NOTIFY for processing + Woo |
Same pattern for order_sync, review_sync, campaign_send, campaign_generate |
Pricing doc conflict (resolve explicitly)
PRICING-AND-USER-GROWTH.md currently proposes Free = 50 AI credits / mo.
This suite design requires Free = ZERO AI credits and gates campaigns / SEO AI / brand AI.
ASSUMPTION (recommended): Align product policy as:
| Plan | Core product AI | Marketing AI (campaigns / SEO / brand kit AI) |
|---|---|---|
| Free | 0 credits (map/export/Woo test only; reverse-trial exception below) | Blocked |
| Starter+ | Included pack per plan | Included; burns same credit wallet (or BYOK) |
| Reverse trial (14d Growth) | Full Growth credits | Full Marketing allowed |
Update pricing-data.ts + plan seeds when implementing — Free must not advertise “50 AI credits” if policy is zero.
2. Product modules
2.1 Email campaigns
Jobs: Draft → preview → schedule/send seasonal or catalog-driven emails using Descrybe catalog + Woo audiences.
Campaign types (MVP → later)
| Type | Trigger / audience seed | Copy source |
|---|---|---|
| Seasonal template | Christmas, Black Friday, New Year, Summer (system templates) | Default prompts + brand kit |
| By category | Products in selected category tree node(s) | Catalog titles/images + formula fields |
| By product set | Explicit SKUs / product IDs / collection | Same |
| Purchase-based | Bought X / not bought Y (needs order sync) | Same + segment filters |
Prompt model (mirror product formulas)
campaign.prompt_mode = "default" | "custom"
campaign.system_prompt_override? // only if custom + plan allows
campaign.user_prompt_vars = { season, category_names, product_snippets, brand_voice, cta_url }
- Default: curated seasonal/category prompts + Brand kit (voice, tone, banned words).
- Custom: editable system/user prompt (Growth+); stored per campaign, versioned.
- Preview: generate N sample emails (1–3) before send; each preview burns credits (or BYOK).
AI path
| Path | When | Billing |
|---|---|---|
| Managed | Default | Debit campaign_copy (+ token packs via existing openai_token_k) |
| BYOK | Growth add-on / Business+ | Completer uses tenant key; no credit burn for inference; still need paid plan feature flag |
Never send AI-generated body without human confirm on first send of a campaign (checkbox “I reviewed the preview”). Resends of an approved version can skip.
Data model (sketch)
marketing_campaigns
id, company_id, name, type, status (draft|scheduled|sending|sent|cancelled)
season_key?, category_ids[], product_ids[]
audience_query jsonb -- segment DSL (see §3)
prompt_mode, prompts jsonb
subject, preview_text, body_html, body_text
schedule_at?, sent_at?
ai_provider (managed|byok), credit_cost_est
created_by, created_at, updated_at
marketing_campaign_sends
id, campaign_id, recipient_hash, email_encrypted_or_token
status (queued|sent|bounced|unsubscribed|failed)
provider_message_id?, error?, sent_at?
marketing_campaign_events -- opens/clicks later (P2)
Worker jobs: campaign_generate, campaign_send_batch (chunked, rate-limited).
2.2 Order sync (Woo → audience)
Goal: Build segments like “purchased product/category X in last 90d AND never purchased Y.”
Sync design
- Extend Woo Integrations config (same
woocommerce_configsrow or siblingintegration_woocommerce_orders):orders_enabled,orders_last_synced_at,orders_cursor(modified_after / page)
- Worker job
woo_order_sync(incremental, bounded pages per tick) - REST:
GET /wp-json/wc/v3/orders(+ line items); map to local tables
woo_customers
company_id, woo_customer_id, email_hash, email_enc, name, marketing_opt_in, unsubscribed_at, ...
woo_orders
company_id, woo_order_id, customer_id, status, ordered_at, currency, total, raw_meta jsonb
woo_order_items
company_id, order_id, sku, product_id_local?, woo_product_id, quantity, category_ids_snapshot[]
Privacy: store emails encrypted at rest; display masked; use email_hash for uniqueness. Prefer Woo marketing_opt_in / billing consent fields when present.
Audience DSL (JSON, server-validated)
{
"op": "and",
"rules": [
{ "type": "purchased_sku", "skus": ["ABC-1"], "since_days": 365 },
{ "type": "not_purchased_sku", "skus": ["ABC-2"], "since_days": 365 },
{ "type": "purchased_category", "category_ids": ["uuid…"], "since_days": 90 },
{ "type": "marketing_opt_in", "equals": true }
]
}
Resolver compiles to SQL with hard filters: unsubscribed_at IS NULL, marketing_opt_in = true, bounce suppression list. Always AND these safety filters server-side (client cannot disable).
ASSUMPTION: Guests without account email are skipped unless order billing email present and opted-in.
2.3 Reviews sync
| Provider | Phase | Notes |
|---|---|---|
WooCommerce product reviews (/wp-json/wc/v3/products/reviews) |
P1 | Same store credentials; incremental sync |
| Placeholder providers (Judge.me, Yotpo, Trustpilot) | P2 | Interface only: ReviewProvider with ListSince(cursor); UI “Coming soon” + webhook stub |
product_reviews
company_id, provider, external_id, product_sku, rating, title, body, author_hash
reviewed_at, synced_at, usable_in_marketing bool
Uses: social proof blocks in campaign templates; optional “mention top reviews” in default prompts; SEO module “products with < N reviews.”
2.4 SEO recommendations (from catalog)
Not a full site crawler in P0. Catalog-first checks that reuse processed products + categories + standard fields:
| Check | Signal | Output |
|---|---|---|
| Thin title / description | Length, uniqueness | Rewrite suggestion (AI = paid) |
| Missing attributes | Required attrs empty | Fill via process job CTA |
| Duplicate titles | Collision count | Merge / differentiate |
| Weak category paths | Depth / orphan | Taxonomy tip |
| Image gaps | No primary image | Upload / feed map tip |
| Keyword coverage | Brand kit primary keywords vs title | Nudge |
seo_recommendation_runs
id, company_id, status, created_at, summary jsonb
seo_recommendations
id, run_id, product_id?, severity, code, message, suggested_action
ai_draft?, applied_at?
- Free: show rule-based list only (no AI drafts).
- Paid: “Generate rewrite” →
seo_recommendcredits / BYOK → optional apply to product fields via existing product PATCH.
2.5 Brand kit
Company-scoped creative constraints for product copy + campaigns.
brand_kits
company_id PK
voice_summary text -- short “we sound like…”
tone_tags text[] -- e.g. friendly, expert, playful
banned_phrases text[]
preferred_phrases text[]
primary_color, secondary_color
logo_url / logo_file_id
fonts jsonb? -- optional
locale_default
updated_at
- Manual edit: available Starter+ (or Free read-only empty kit?). ASSUMPTION: Free can view empty kit + upgrade CTA; edit + AI-assist = paid.
- AI assist (“Infer brand from catalog”): paid; feature
brand_kit_ai. - Injected into processing enhance prompts and campaign defaults (single source of truth).
UI: Settings → Company or Marketing → Brand kit (prefer Marketing → Brand kit with deep-link from Integrations).
3. Plan gating & Free tier
3.1 Feature matrix
| Capability | Free | Starter | Growth | Business+ |
|---|---|---|---|---|
| SKU / feed / storage meters | Per pricing ladder | ✓ | ✓ | ✓ |
| AI credits | 0 | Pack | Pack | Pack / BYOK |
| Product processing AI | ✗ (unless reverse trial) | ✓ | ✓ | ✓ |
| Marketing nav (read-only teasers) | Teaser + upgrade | ✓ | ✓ | ✓ |
| Campaigns create/send | ✗ | ✓ (caps) | ✓ | ✓ |
| Campaign AI generate | ✗ | Credits | Credits/BYOK | ✓ |
| Order sync | ✗ (or read-only test 1 page?) | ✓ | ✓ | ✓ |
| Audience “bought X not Y” | ✗ | ✓ | ✓ | ✓ |
| Reviews sync (Woo) | ✗ | ✓ | ✓ | ✓ |
| SEO rule-based | ✓ (limited rows) | ✓ | ✓ | ✓ |
| SEO AI rewrite | ✗ | ✓ | ✓ | ✓ |
| Brand kit edit | ✗ | ✓ | ✓ | ✓ |
| Brand kit AI | ✗ | ✓ | ✓ | ✓ |
| BYOK | ✗ | ✗ | Add-on | Included |
ASSUMPTION: Starter is the smallest plan that unlocks Marketing Suite send + order sync. Free stays acquisition for feed aha, not email ESP.
3.2 Enforcement seams (reuse billing)
Extend internal/billing:
// Pseudocode — design only
AssertFeature(ctx, companyID, "marketing.campaigns")
AssertFeature(ctx, companyID, "marketing.order_sync")
AssertCanSpendCredits(ctx, companyID, feature, est)
- Plan row gains
features jsonb(orplan_entitlementstable). - API returns
403+ machine codefeature_gated/insufficient_creditsfor UIUpgradeBanner. - Reverse trial: temporary Growth entitlements; day 15 → Free (0 credits, Marketing hard-off). Persist draft campaigns read-only.
3.3 Caps (Starter starter-limits)
| Meter | Starter (example) | Growth |
|---|---|---|
| Campaigns / mo | 5 | 50 |
| Recipients / send | 2_000 | 25_000 |
| Order sync lookback | 180d | 730d |
| SEO AI rewrites / mo | 50 | 1_000 |
Exact numbers tunable; enforce server-side.
4. Integration style (UX IA)
4.1 Settings → Integrations
Replace scattered /woocommerce as the only integration home. Keep /woocommerce as redirect → Settings Integrations for one release if needed.
Settings tabs: profile | company | team | api-keys | integrations
Integrations cards:
- WooCommerce — store URL, keys, test connection, product sync toggle, order sync toggle, reviews sync toggle
- Email sending — provider (Resend / SES / SMTP / Postmark), from-domain, verification status, rate limits
- AI keys (BYOK) — Growth+; encrypted; never logged
- Reviews providers — Woo (active) + placeholders
4.2 Marketing nav
New top-level group (after Export Feeds / before Billing):
Marketing
├─ Campaigns
├─ Audiences
├─ Reviews
├─ SEO
└─ Brand kit
Free users see routes with locked empty-states + upgrade CTAs (not 404).
5. Safety (non-negotiable)
| Rule | Implementation |
|---|---|
| No send without verified provider | email_provider.status == verified AND DNS/domain checks passed; else Send button disabled + API 412 email_not_verified |
| Opt-out / unsubscribe | Every email: List-Unsubscribe + one-click POST; landing page; set unsubscribed_at; suppress forever until re-opt-in via Woo/consent |
| Opt-in required | Audience resolver always requires marketing consent |
| Rate limits | Per-company send RPM/RPH; provider caps; exponential backoff; circuit breaker on bounce rate > threshold |
| PII | No emails in logs; hash + encrypt; admin support tools mask by default |
| Double opt-in for imported lists | If CSV import ever added (P2): require confirmation — Woo-sourced opted-in only in P0/P1 |
| Abuse | Cap Free teasers; paid caps; platform admin kill-switch per company |
| Legal copy | Footer template mandatory (company address, unsubscribe) before schedule |
Platform invite SMTP (internal/mail) must not be used for marketing blasts.
6. UX principles
- Happy path wizard: Pick template (BF/Christmas) → pick category/products → auto audience suggestion → generate 1 preview → connect email if missing → schedule.
- Advanced accordion: custom prompts, raw audience DSL, UTM params, send-time optimization (P2).
- Reuse catalog chrome: product pickers, category tree, UpgradeBanner, processing job progress patterns.
- Empty states teach the core path: “Connect Woo + sync orders to target buyers of X.”
- Credit honesty: show estimated credit cost before Generate; BYOK badge when active.
7. Interactive tutorial (activation)
Extend the pricing doc’s ≤15 min aha into a guided checklist (dashboard + optional spotlight tour):
| Step | Route / action | Done when |
|---|---|---|
| 1. Add feed | /feeds |
Feed created with URL/file |
| 2. Map fields | /feeds/{id}/mapping |
Required fields mapped (auto-map OK) |
| 3. Process sample | /processing |
≥10 products completed (credits: trial/paid) |
| 4. Export or Woo push | /export-feeds or Integrations |
Export generated or Woo test sync OK |
| 5. Brand kit (paid) | /marketing/brand-kit |
Voice saved or skipped with “later” |
| 6. Campaigns (paid) | /marketing/campaigns |
Draft campaign created (send optional) |
- Persist
onboarding_progressoncompany_settings. - Free users complete 1–4; 5–6 show locked steps with value props.
- Tutorial deep-links must survive refresh; dismissible; “Replay tour” in help.
Aligns with process-and-sell-summary.md happy path + Marketing as step after export.
8. Technical architecture (v2 packages)
apps/api/internal/
marketing/ # campaigns, audiences, brand kit, seo services
integrations/
email/ # provider interface: Send, VerifyDomain, Webhooks
reviews/ # ReviewProvider
billing/ # + AssertFeature, feature costs
woocommerce/ # + OrdersClient, ReviewsClient, sync workers
apps/api/sql/schema/
01x_marketing.sql # tables above
01y_integrations_email.sql
apps/web/src/routes/
marketing/campaigns/
marketing/audiences/
marketing/reviews/
marketing/seo/
marketing/brand-kit/
settings/ # + integrations tab
unsubscribe/[token]/ # public
Completer sharing: marketing generation calls the same processing.Completer interface; inject company BYOK client when entitlement + key present.
Idempotency: send batches keyed by (campaign_id, recipient_hash); sync cursors monotonic.
9. API sketch (dashboard + future public)
| Method | Path | Notes |
|---|---|---|
| GET/PATCH | /api/integrations/woocommerce |
Existing + order/review flags |
| POST | /api/integrations/woocommerce/sync-orders |
Enqueue |
| GET/PATCH | /api/integrations/email |
Provider config |
| POST | /api/integrations/email/verify |
Start verification |
| GET/PATCH | /api/marketing/brand-kit |
|
| GET/POST | /api/marketing/campaigns |
|
| POST | /api/marketing/campaigns/{id}/generate |
Credits gate |
| POST | /api/marketing/campaigns/{id}/schedule |
Requires verified email |
| POST | /api/marketing/audiences/preview |
Count + sample (masked) |
| POST | /api/marketing/seo/runs |
|
| POST | /api/public/unsubscribe |
Token auth |
Public API v1 exposure of Marketing = Business+ / later; dashboard session auth first.
10. Risks & open decisions
| Topic | Risk | Recommendation |
|---|---|---|
| Free AI credits vs pricing doc | Conflicting promises | Ship Free = 0 AI; update pricing doc + pricing-data.ts |
| Becoming an ESP | Deliverability / spam liability | Verified domain only; start with low caps; consider “export to Klaviyo” P2 |
| Order PII | GDPR | Encrypt, retention policy, delete-with-company, DPA |
| Woo permission scopes | Keys may lack orders/reviews | TestConnection checks endpoints; clear UI errors |
| Custom prompts abuse | Prompt injection / brand damage | Length limits; strip tools; require preview ack |
Open questions (non-blocking for P0 scaffolding):
- First ESP: Resend vs SES vs tenant SMTP only?
- Starter recipient cap exact number?
- Free SEO rule-based: show all issues or cap at 20?
11. Sibling-agent checklist
P0 — foundation (do first; unblocks everything)
- P0.1 Align Free plan: 0 AI credits; Marketing feature flags on
plans; updatepricing-data.ts+ seeds; reverse-trial keeps Growth marketing. - P0.2
billing.AssertFeature+ credit feature names (campaign_copy,seo_recommend,brand_kit_ai); wireUpgradeBannercodes. - P0.3 Settings → Integrations tab; move Woo config UX there (redirect old
/woocommerce). - P0.4 Marketing nav shell + locked empty states for Free.
- P0.5 Brand kit schema + CRUD (manual fields only).
- P0.6 Email provider integration model + verified-only send gate (even if provider is stubbed behind interface).
- P0.7 Campaign draft CRUD + default seasonal templates + generate-preview job (no mass send yet).
- P0.8 Unsubscribe token endpoint + suppression list.
- P0.9 Woo order sync (incremental) +
woo_orders/ items / customers tables. - P0.10 Audience preview: purchased X / not purchased Y + opt-in hard filters.
- P0.11 Onboarding tutorial steps 1–4 wired to real completion signals; 5–6 gated.
- P0.12 Safety tests: cannot send if unverified; cannot include unsubscribed; Free cannot generate campaign AI.
P1 — sellable Marketing MVP
- P1.1 Campaign schedule/send batches via verified provider + rate limits + bounce basic handling.
- P1.2 Custom vs default prompts UI; credit estimate + BYOK seam (if BYOK platform work ready).
- P1.3 Woo product reviews sync + Reviews Marketing page.
- P1.4 SEO recommendation runs (rule-based + paid AI rewrite).
- P1.5 Brand kit injection into product enhance + campaign defaults.
- P1.6 Cap enforcement (campaigns/mo, recipients/send).
- P1.7 Tutorial steps 5–6 + “first Black Friday campaign” recipe.
- P1.8 Ops: kill-switch, send metrics on billing overview.
P2 — expand
- P2.1 Placeholder review providers (Judge.me / Yotpo) + webhooks.
- P2.2 Open/click tracking; A/B subjects.
- P2.3 Export audience / campaign to Klaviyo/Mailchimp.
- P2.4 Site crawl SEO (beyond catalog).
- P2.5 Public API for campaigns.
- P2.6 Multi-language campaign variants from catalog locales.
- P2.7 AI “infer brand kit from best products.”
12. Verification expectations (when built)
- Unit: audience SQL compiler, feature gate matrix, unsubscribe token.
- Integration: Woo orders pagination mocked; send blocked without verify.
- UI: Free user sees locks; Starter can preview generate with credits.
- No claim-done without
AssertFeaturecoverage on every mutating Marketing route.
13. Rollback
- Feature flags
MARKETING_ENABLED(platform) + per-plan entitlements. - Schema additive only; drop via goose Down if needed.
- Disabling flag hides nav and 404s APIs without deleting drafts.
Related docs
- Sibling pricing:
f:/laragon/www/_MY/descrybe/PRICING-AND-USER-GROWTH.md - process-and-sell-summary.md
- features.md · status-and-gaps.md · security-notes.md