Skip to content

Integrations & secrets

Tessule can push the people in a table — or the result of a SQL view — into a marketing platform as a synced audience, so the segment you define once in your CRM becomes the list your email platform sends to. Alongside that sits the workspace secret store: a write-only vault for API keys and webhook credentials referenced by features that call other systems on your workspace’s behalf. Both live in Workspace Settings and both handle third-party credentials, so both are admin territory.

If you only remember one thing: audience syncs are a one-way push. Your workspace owns the membership; the provider is told who belongs and is never asked. Nothing flows back — no consent, no engagement, no unsubscribes — and Tessule never deletes a profile or writes an unsubscribe on the provider side.


  • View connections and syncs — requires integrations:read, granted to admins and members by default (not readers). The metadata carries no credential material, and a member configuring an email campaign needs to see which connections exist.
  • Create, edit, test, run, and delete connections and syncs — requires integrations:manage, granted to workspace admins only. Managing an integration means holding a third-party credential and sending customer data to another vendor, the same trust tier as secrets:manage.
  • Create, rotate, and delete secrets — requires secrets:manage, admin-only.

These are ordinary actions in the roles model: a custom role can grant any of them individually.


A connection is one marketing-platform account: a provider, a name of your choosing (unique per workspace), and an API key. Manage them in Workspace Settings → Integrations, or via GET /integrations/connections and POST /integrations/connections.

Three providers are supported:

Provider Credential to paste An “audience” is called
Klaviyo Settings → API keys → Private API key. Needs read and write access to Profiles and Lists. a list
Mailchimp Account → Extras → API keys. The key ends with a datacenter suffix such as -us14 — paste it whole. an audience
Omnisend Store settings → Integrations & API → API keys. Needs the Contacts scope. a tag

The API key is stored encrypted at rest (with the same cipher as workspace secrets) and is never returned by any endpoint — not in the connection list, not in test results, not in error messages. If a key is lost, you don’t retrieve it; you rotate it: PUT /integrations/connections/{connectionId} accepts a new name and/or a replacement credential.

POST /integrations/connections/{connectionId}/test (the test button on the connection row) makes one authenticated, read-only call to the provider and records the outcome on the connection:

  • ok — the credential works. The result includes an accountLabel (the provider-side account name) so you can confirm you connected the right account, not just an account.
  • error — the provider rejected the call; the failure text is shown and stored as the connection’s lastError.

A connection’s status is one of unverified (never proven), ok, or error. A failed sync run also updates it.

Deletion is refused while any audience sync still references the connection — delete the syncs first, so none is silently orphaned. Anything already pushed to the provider stays there.

Each workspace can hold up to 10 connections by default (an adjustable limit — see organization administration).


An audience sync activates a table or SQL view as an audience on a connected platform. The source plus its email field is the audience definition: define the segment once in your workspace, and the provider-side list follows it.

Table or SQL view email field + mapped fields Audience sync diffs against its ledger: added / changed / removed Provider audience Klaviyo / Mailchimp / Omnisend One direction only — consent and engagement never flow back
A sync reads its source, compares against what it last pushed, and updates the provider-side list — never the reverse.

Create one from Workspace Settings → Integrations → audience syncs (or POST /integrations/audience-syncs). You choose:

  • Platform connection — which connected account to push to.
  • Source — a table, or a SQL view holding the segment. Fixed after creation — moving a sync to a different source would orphan its ledger. Views are usually the right choice: the view is the audience definition.
  • Email field — the column holding the address. It’s the identity key: how a row is matched to a provider profile.
  • Destination audience — picked from the lists that already exist on the provider side (GET /integrations/connections/{connectionId}/audiences populates the picker).
  • Fields to send (optional) — a mapping from source columns to provider-side profile properties (Klaviyo profile attributes, Mailchimp merge fields, Omnisend custom properties).
  • Sync is active — a disabled sync refuses to run; queued runs of one are skipped, not executed.

Each workspace can hold up to 25 syncs by default (adjustable limit).

There is no automatic schedule. A sync runs when you run it: POST /integrations/audience-syncs/{syncId}/run (the run button) enqueues a run and returns immediately with a run id; the work happens in the background. Only one run per sync may be in flight — starting another while one is queued or running is refused with a conflict, because two concurrent runs could push the same profile twice.

Each run is a snapshot-and-diff against the sync’s ledger (what it last pushed):

Category Meaning Provider call
added In the source, not yet on the list Upsert
changed On the list, but a mapped field’s value changed Upsert
unchanged On the list with identical data None
removed In the ledger, no longer in the source Removed from the list only
failed The provider rejected that row

Steady state is free: a run over an unchanged audience makes zero provider calls. “Removed” never means a profile deletion or an unsubscribe — the person simply stops being on that list.

The sync list shows each sync’s last run: status (queued, running, completed, failed, cancelled), the added/changed/removed/unchanged/failed counts, when it ran, and the member count currently tracked in the ledger. A failed run records its error on the sync (and on the connection when the credential was the problem).

Failure handling distinguishes two cases:

  • Transient failures — provider rate limits, outages — are retried automatically a bounded number of times.
  • Authentication failures are never retried: a rejected credential stays rejected, and hammering a provider’s auth endpoint is how an account gets locked. Rotate the key, test the connection, then run again.

DELETE /integrations/audience-syncs/{syncId} removes the sync and its ledger. Provider-side list membership is left exactly as it is — nobody is mass-removed from a marketing list because a CRM object was deleted. That kind of removal is impossible to undo, so it deliberately never happens implicitly.


Secrets are named values — webhook signing keys, third-party API keys — referenced by name without the value ever appearing in configuration or error messages. Manage them in Workspace Settings → Secrets (visible to holders of secrets:manage), or via the API.

  • Naming: UPPER_SNAKE_CASE, starting with a letter, up to 64 characters, unique per workspace (e.g. HOOK_API_KEY). Values up to 4,096 characters.
  • Write-only: GET /secrets returns metadata only — name, who created it, when it was last updated. No endpoint ever returns a secret’s value. If a value is lost, rotate it: PUT /secrets/{name} replaces the value in place, keeping every reference to the name working.
  • Referencing: the single-action automations that referenced secrets by name have been replaced by Flows (multi-step automation), which consume the same store; documentation arrives with the new builder.
  • Deleting: DELETE /secrets/{name} removes it immediately. Update anything that still references the name first.
  • Limit: 20 stored secrets per workspace by default (adjustable).

The same rules apply to integration connection keys and workspace secrets:

  • Encrypted at rest with a workspace-scoped cipher.
  • Write-only on the wire — no read path exists, by design, so neither a compromised browser session nor an over-broad API token can exfiltrate a stored credential through the API.
  • Test results and error messages carry provider text, never credential material.
  • Only integrations:manage / secrets:manage holders (workspace admins by default) can write them, and only the same tier can point them at new destinations.

Connections: GET /integrations/connections, POST /integrations/connections, PUT /integrations/connections/{connectionId}, DELETE /integrations/connections/{connectionId}, POST /integrations/connections/{connectionId}/test, GET /integrations/connections/{connectionId}/audiences. Audience syncs: GET /integrations/audience-syncs, POST /integrations/audience-syncs, PUT /integrations/audience-syncs/{syncId}, DELETE /integrations/audience-syncs/{syncId}, POST /integrations/audience-syncs/{syncId}/run. Secrets: GET /secrets, POST /secrets, PUT /secrets/{name}, DELETE /secrets/{name}.


Q: Can I sync unsubscribes or engagement data back from the platform?

No. The integration is deliberately one-way: membership is pushed out, and consent/engagement never flow back through these syncs. Manage unsubscribes on the provider side, where the legal record of consent lives.

Q: Why can’t I change a sync’s source table?

The ledger — the record of what was pushed for which row — is keyed to the source. Re-pointing the sync would make that history meaningless. Delete the sync and create a new one against the new source.

Q: Why does the provider audience picker fail with a quota error?

Calls to the provider (test, audience listing) are rate-limited per workspace. Wait a moment and retry.

Q: Someone left our company — do they disappear from the mailing list?

Only if your source says so. If the row leaves the table or view (or its email field empties), the next run counts them as removed and takes them off the list. If the row remains, so does the list membership. And if you delete the whole sync, the provider keeps the list as-is.

Q: A member can see our connections — can they run a sync?

No. integrations:read shows metadata only. Creating, editing, running, and deleting — anything that moves data or touches a credential — requires integrations:manage, which only admins hold by default.

Open the app