Skip to content
Cowliss is in beta: features and pricing may change.

Data protection and your responsibilities

Every Cowliss organization is the controller of its own data: you decide what your users’ data is for, you establish the lawful basis (GDPR Art. 6), and you answer to your users. Cowliss is the processor (Art. 28): it stores and moves personal data only on your documented instructions. The contract layer lives under Legal; this page is the practical half: the defaults you accept by using Cowliss, and the tooling you get for data rights.

Each default below is a documented instruction: it reads as something you instruct Cowliss to do, not a policy Cowliss imposes on you.

Section titled “Default 1: ingestion is unconditional, consent gates sends”

Cowliss ingests every event you send it, regardless of the profile’s consent state. Consent is enforced at the moment of action: every send checks the consent map before an email whose purpose a recipient can withhold, or a marketing automation’s webhook, goes out and records skipped_consent in the delivery log when the purpose is off.

The split is deliberate. Storing what your apps observed is a different act from contacting the person, and gating at send time means a consent change takes effect everywhere at once, including for automations already in flight. If your lawful basis requires not storing someone’s events at all, don’t send them.

One carve-out, also part of this default: templates that declare bare transactional (a receipt, a password reset) bypass consent by design, because a receipt must not vanish just because someone once left a newsletter. They still stop on suppression, quota, and a sending pause.

Default 2: a purpose is why you are contacting someone

Section titled “Default 2: a purpose is why you are contacting someone”

Every email declares a purpose on its template, and it answers one question: why this message goes out. A purpose is a path under one of two roots:

  • marketing.<name>, such as marketing.newsletter: off until you record a yes.
  • transactional.<name>, such as transactional.weeklyReport: mail about the person’s own account that they may still want to stop. On until they turn it off.
  • transactional, bare: a receipt, a password reset, a verification link, a security notice. It must reach the person whatever they chose, so nobody can withhold it.

The names are yours. You declare each one in cow.json with the label a recipient reads, and it belongs to that app alone: a profile belongs to one app, so it answers only its own app’s purposes, and two apps may declare the same path and mean different things. What a name means is controller-defined, so publish that meaning to your recipients. Cowliss never interprets it; it only honors the answers.

A send under a name needs the name and its root both on. Granting a name turns its root on, so one key is a complete signup. Turning a root off turns off every name under it. Bare transactional is never asked: the transactional root turns off only the names under it, never bare mail such as a receipt, while everything that protects the address itself (suppression, your credit, a sending pause) still applies.

A new profile starts with every marketing purpose off: being ingested is not agreeing to be marketed to, so the grant has to come from somewhere. A transactional name starts on.

An automation declares only a root, marketing or transactional. That decides how it enrolls, and whether its webhooks need the recipient’s yes to marketing. The emails it sends carry their own purposes.

A promotion is not a transactional purpose. Nothing mechanical stops you declaring a promotion as transactional.<name>, which starts on, or as bare transactional, which nobody can leave. Which purpose a message belongs to is a judgement about your own message, so it is yours to make and yours to answer for. Cowliss honors the purpose the template declares; it cannot read the mail. A promotion is marketing, whatever the template calls it. Bare transactional is only for mail the person cannot do without, and anything they could do without gets a transactional.<name>, so they can at least leave it.

Set consent per user on the identify call that already carries their traits, through the consent endpoint, or in the dashboard’s consent editor. Passing it on identify is what lets your own signup checkbox be the record:

await cow.identify({
identifiers: { userId: "user_1" },
traits: { email: "[email protected]" },
consent: { "marketing.newsletter": form.newsletter },
});

The field is a merge patch: a purpose you omit keeps the value it had, and omitting consent entirely changes nothing, so a later identify is never an accidental revocation. Granting only marketing mails nobody, because every marketing email names a purpose: grant the name. Two automations revoke consent on your behalf: a spam complaint on marketing mail turns off marketing and every name under it (on a transactional.<name> email, that name alone; on bare transactional mail, nothing), and the one-click unsubscribe link every send someone can leave carries turns off the purpose that mail was sent under. “Unsubscribe from everything”, on the preferences page the footer links to, turns off both roots instead. Both write a system.consent_revoked system event carrying the purpose, so the user timeline always answers “when and why did sends stop”.

Art. 7(1): where processing is based on consent, the controller must be able to demonstrate it. Cowliss stores the consent state you supply opaquely. It does not know what your user saw or agreed to. Capturing the agreement (your form, your wording, your timestamp) is yours; the consent map plus the system.consent_revoked event stream is the storage Cowliss contributes to your proof.

Every email whose purpose someone can withhold carries a signed one-click unsubscribe in its headers. Unless your template renders its own links, it also carries a footer saying who it was sent to and why (the purpose’s label), with “Update your email preferences” and “Unsubscribe” (this purpose), then your app’s name, website and postal address. A link only opens a confirmation page, because mail scanners follow links. Keep me subscribed is the page’s prominent choice and changes nothing; the Unsubscribe button makes the change.

Leaving a purpose turns off that purpose alone. Leaving everything turns off both roots and every name under them, and the page says that account mail such as receipts still arrives. Bare transactional mail carries no link, because nobody can withhold it.

The page after the click offers Resubscribe for 48 hours. It puts back exactly what that click turned off, never a purpose the recipient had not granted or had already turned off, and writes a system.consent_granted event for each purpose that is on again. The same page asks, optionally, why they left, and records the answer as a system.unsubscribe_feedback event on their profile: a reason they picked and up to 500 characters they typed, which is personal data like any other event property. Every email a recipient can leave also links to a hosted preferences page, where they switch each of your app’s purposes on or off themselves, and your app can send them there too. Each change writes a system.consent_revoked or system.consent_granted event with reason preferences. Your own app still records consent it collects with identify.

Data-rights requests land on you; Cowliss’s job is making each one call:

  • Erasure: DELETE /v1/users/{profileId} removes a person everywhere, identifiers, events, and automations they are in included. One request answers the request. Also a dashboard action on the user page.
  • Export: GET /v1/users/{profileId}/export returns one JSON document: profile, identifiers, traits, consent, suppressed addresses, event history, and automation/delivery history.
  • Retention: one “keep events N days” setting for the organization (90 days unless you set another, 18 months at most), applied as each event is written. A write whose timestamp is older than the window is not kept in the raw event log but still counts toward segments; one more than 18 months old is refused with 422. The window governs the raw event log (the event feed and exports); segments do not depend on it, because they count what each person did in a summary kept for as long as the profile, and erased with it. See the settings reference.
  • Self-service for your recipients: the signed one-click unsubscribe every email a recipient can leave carries, as above.

All of it is in the users API reference. Erasure, export, and retention are API-and-dashboard operations; the CLI and MCP cover the read side plus consent edits.

Sign-up asks for express acceptance of the terms of service and privacy policy, which incorporate the DPA, so the controller/processor split binds every organization from its first event.