Skip to main content
This page is for people evaluating the project: what it takes for the assumptions to hold, and where the product knowingly stops.

Assumptions

  • Single team, single tenant. Campaign Assistant is an internal workspace for one marketing team at DME. There is no tenant concept, no multi-org separation, and no self-serve onboarding. Everyone who signs in sees the same data.
  • The numbers are seeded, not real. The seed script creates two accounts and 280 synthetic customers so segments, dashboards and campaigns have something to work with. It is idempotent, so re-running it is safe. Any evaluation of the analytics reads synthetic data unless you import your own via the CSV endpoint first.
  • A working environment looks like this: Node 24, pnpm, and PostgreSQL (the dev path runs it in Docker). Deployment targets a single VPS. See the deployment reference.
  • A model is optional. With no provider key the API runs a deterministic local generator that exercises the full pipeline. Real models are an add-key-away change; see add an LLM provider.
  • The compressed domain: a “customer” is a person with attributes and a location, a “segment” is a saved set of conditions, and a campaign moves through draft → ready → sent. Terminology and invariants are documented in the glossary.

Limitations

These are boundaries the project draws deliberately, not gaps left by accident.
  • Drafted copy is not fact-checked. The model is instructed not to invent figures, and its output is validated for shape, but nothing verifies its content against the database. A draft can mention an offer that does not exist. Mitigation: drafts never cross out of draft status without a person. See AI usage.
  • The chat assistant has no database access. It answers only from the thread context (the last 12 turns), so it cannot compute or look up customer metrics you have not put in front of it. Send it nothing you would not paste into a shared document.
  • Provider failure is not silent. There is no cross-provider failover: if the bound provider is unreachable, the request is retried on a deadline and then answered with 503/llm_unavailable. You cannot receive copy from a different model than the one configured, with nothing telling you.
  • Idempotency records are never pruned. Every POST /campaigns/generate keeps its replayed response for its authenticated caller; storage grows with use and is currently unbounded.
  • Sessions lean on localStorage. Both tokens live there by design — no payment data exists to protect — and rotation bounds the blast radius. See sessions and authentication.
  • Schema changes require deliberate action in production. The seed script owns the schema; deployments run it, but production refuses to unless ALLOW_SCHEMA_PUSH=true. A schema drift therefore fails loudly instead of migrating a live database by surprise.
  • Copy languages are English and French. Campaign copy is written in one or the other, never both; the UI is localised to the same two. Anything else is out of scope today.
  • Deployment is a single shared VPS. There is no horizontal scaling, no CDN in front of the app, and the API reference is served from the installed package so it works on an isolated network.
Last modified on October 6, 2026