Concepts
Cowliss is a customer data layer with one loop: event in → durable journey per user → email/webhook out. Everything else exists to make that loop trustworthy in a multi-tenant product.
The pipeline
Section titled “The pipeline”your apps ──identify/track──▶ ingestion API (/v1, API-key auth) │ ┌────────────────────┼───────────────────────┐ ▼ ▼ ▼ Postgres ClickHouse segment engine profiles, traits, append-only event recompute on every identifiers, consent, stream (per app, write; transitions emit catalog, segments, with historical system.segment_entered / releases, journeys … timestamps) system.segment_exited │ │ ▼ ▼ journey trigger ──▶ journey executions (raw events and one durable execution per segment entries) profile per journey, running the deployed release │ ▼ send activities (gates checked at execution time) ──▶ email via SES / SNS feedback, signed webhooksThe pieces
Section titled “The pieces”- Apps and sources: an app is one product you run, pinned to one environment at creation, and the attribution unit: journey triggers and segment predicates filter by its
appId. A source is one inbound pipe into an app, either theapisource the app is created with or a provider such as Clerk, and everyidentify/tracknames thesourceIdit arrives through. The server derives the app and stamps both. One org cleanly holds a whole portfolio. See Apps and sources. - Environments:
developmentandproductionare a hard partition inside the org. The environment lives on the app, ingestion stamps it on every write, and profiles, events, settings, and delivery all stay on their side. See Environments. - Profiles and events:
identifyupserts a profile (Postgres),trackappends an event (ClickHouse). Events are immutable; profiles merge RFC 7386-style. - Identity: Cowliss generates every profile id (
usr_…); every call names who it is about with anidentifiersmap (userId,anonymousId,clerkId,email). Resolution creates a profile, claims new identifiers for a known one, or merges the profiles when a call names two. There is no alias call. See Identity. - Email addresses: an
email_addressesledger per org records every address a profile has held and whether it is verified, bylink(Cowliss’s own signed link),imported(a caller asserting it), orprovider(an auth provider’s own flag). Taking on an unverified address emitssystem.email_registered; verifying one emitssystem.email_verifiedcarryingmethod. The profile’semailVerifiedAtandemailVerifiedAddresstraits are system-maintained: a caller writing them gets a 422, changingemailto a new address clears verification, and changing it back to an address the ledger already verified restores it. - Catalog and governance: event/trait names are accept-by-default with a violation queue for the unknown, plus a deny-list and a per-environment
ingestionPolicy(permissive/strict) for hard control. Rejection has exactly two sources: denied names (422) and canonical-trait format validations. - Segments: flat predicate lists over traits and event history, recomputed synchronously on every write. Membership transitions emit
system.segment_entered/system.segment_exitedsystem events, so journeys treat cohorts and raw events uniformly. See Segments. - Projects and releases: journeys and email templates are TypeScript in your repo, in a project the
cowCLI builds and pushes. A push makes an immutable, numbered release;cow deploy --envmakes one release an environment’s current code. Executions stay pinned to the release they started on. See Projects. - Journeys: one durable execution per profile per journey per environment, with durable timers for waits and later events delivered as they arrive. Journey code runs in a sandbox with no I/O, so every effect goes through a journaled capability call. See Journeys.
- Delivery: email directly through AWS SES behind an
EmailProviderport, with delivery feedback arriving as SNS HTTPS notifications on a public route; webhooks as Standard-Webhooks-signed JSON POSTs to dashboard-managed destinations. Every attempt is a row in the delivery log:sent,delivered,bounced,complained,failed, or a typedskipped_*. - Send gates: marketing sends check, in order: journey enabled, org sending pause, address suppression, org credit, per-user frequency cap, purpose consent, from-domain. Transactional sends skip only consent and the frequency cap.
- Billing: a monthly free allowance plus a prepaid wallet; one weighted spend over events, emails, webhooks, and journey runs; a hard stop at zero credit. Money is the only gate: there are no plans or feature tiers. See Billing.
- Compliance: per-purpose consent (
emailMarketing,dataProcessing), one-click unsubscribe, one-call erasure across Postgres + ClickHouse + Temporal, JSON export, per-environment event retention via ClickHouse TTL.
Agentic surfaces
Section titled “Agentic surfaces”The dashboard, the cow CLI, and the MCP server all talk to the same typed REST API, so admin parity is structural: anything you can click, an agent can call. These docs are built to be read by agents too: /llms.txt, per-page markdown, and /openapi.json. See For agents.
Versioning
Section titled “Versioning”Every public contract is versioned and frozen at its version: the /v1 API, the journey guest protocol (major 1, stamped into every release), the envelope and error-code enum, webhook payloads stamped v: 1, and stable CLI/MCP command names. New ideas ship as a new version alongside the old, never as a breaking change.