Files
twenty/packages/twenty-apps/internal/self-hosting
Paul Rastoin fe442fe5fe chore(apps): bump sdk to 2.19.0-alpha.1 and require twenty server >=2.19.0 (#22601)
## Summary

- Bumps `twenty-sdk` / `twenty-client-sdk` to the exact `2.19.0-alpha.1`
prerelease for the apps under `packages/twenty-apps` that actually
target a mutated standard identifier, and refreshes their lockfiles.
- Declares `"engines": { "twenty": ">=2.19.0" }` in those apps so
pre-2.19 servers refuse to install or upgrade to the rebuilt packages.

Only apps that reference a standard object's **system-field** universal
identifier, define a **relation into** a standard object, or call the
field-UID derivation helper need 2.19 (the identifiers those touch
changed from hardcoded UUIDs to deterministic hashes). Apps that only
define their own custom objects, or add plain scalar fields to a
standard object via its stable object-level id, were left on their prior
SDK pins. Currently bumped: `postcard`, `self-hosting`,
`twenty-partners`, `call-recorder`, `people-data-labs`,
`twenty-last-contact`.

## Context

Follow-up to #22565 (deterministic system field universal identifiers)
and #22599 (SDK prerelease bump).

Packages built with SDK ≤ 2.18 carry legacy system field identifiers and
are rejected by servers running `main`. Rebuilding with the 2.19 SDK
fixes that — but a rebuilt package must not be *upgraded into* by a 2.18
server, since 2.18 has no deterministic-identifier validation and would
diff the changed system field identifiers as a destructive delete +
create (the 2.19 backfill has not run there yet).

The `engines.twenty` constraint closes that gap: `doInstallApplication`
validates it via `validateServerCompatibility` before any mutation, and
this check has shipped since ~2.10, so every 2.18 server enforces it.
Resulting matrix:

- 2.18 fresh install of a rebuilt app: works, converges as a no-op once
the 2.19 backfill runs
- 2.18 upgrade of an existing install: rejected with
`SERVER_VERSION_INCOMPATIBLE` before any mutation
- 2.19 (post-backfill) install/upgrade: syncs cleanly

## Expected CI failures

**The `CI Twenty Apps` integration-test jobs are expected to fail on
this PR** (e.g. `people-data-labs`, `twenty-partners`). This is a
server-version mismatch, not an app bug — lint, typecheck and unit tests
all pass:

- The integration step spawns a real Twenty server from Docker Hub
`twentycrm/twenty-app-dev:latest` and runs `twenty dev` to sync each
app's metadata into it.
- `latest` currently resolves to **v2.18.5** — no `2.19` image is
published to Docker Hub yet.
- These apps now reference 2.19's **deterministic system-field universal
identifiers** (e.g. `company.createdBy`, `opportunity.createdAt`). A
2.18 server still carries the legacy identifiers, so the sync rejects
every 2.19-derived reference with `INVALID_VIEW_DATA` /
`FIELD_METADATA_NOT_FOUND` ("Field metadata not found").
- The failure surfaces as low-level field errors rather than a clean
`SERVER_VERSION_INCOMPATIBLE` because the `engines.twenty` gate
(`validateServerCompatibility`) only runs on the `app:install` / publish
paths — **not** on the `twenty dev` dev-sync path the integration tests
use.

These jobs will go green automatically once `twenty-app-dev:2.19` is
published to Docker Hub (or once CI pins the spawn action's
`twenty-version` to a 2.19 tag).

## Intentionally not included

- App `version` fields are untouched; they'll be bumped at publish time.

## Test plan

- [x] Refresh each bumped app's `yarn.lock` (`2.19.0-alpha.1` is now on
npm)
- [ ] Rebuild one app manifest and verify default field identifiers
match `getFieldUniversalIdentifier`
- [ ] Verify a 2.18 server rejects an upgrade to a rebuilt package with
`SERVER_VERSION_INCOMPATIBLE`
- [ ] Re-run `CI Twenty Apps` integration jobs once a
`twenty-app-dev:2.19` image is available

Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22601?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-07 13:03:43 +02:00
..
2025-11-21 14:10:51 +01:00

Self Hosting

The Self Hosting app collects sign-up telemetry from self-hosted Twenty instances and turns it into structured CRM records. Each time a user signs up on a self-hosted instance, the app records who they are, which instance they belong to, and automatically links them to a matching Person in your workspace.

What it does

  • Receives sign-up events from self-hosted instances through a public telemetry webhook.
  • Creates or updates a Self Hosting User record for each real sign-up, keyed by email address.
  • Automatically matches every Self Hosting User to an existing Person by email, creating a new Person when none is found, and keeps that link up to date as the data changes.
  • Filters out test and example sign-ups so they never enter your CRM.
  • Carries a rich set of person and company enrichment fields so each sign-up can be augmented with firmographic data.

What it adds to your workspace

Self Hosting User object

A new object, Self Hosting Users, stores one record per self-hosted sign-up. Its fields include:

  • Identity: Name, Email, Domain, Locale. The webhook only populates Name, Email, and Locale; Domain exists on the object but is not filled in by the sign-up flow.
  • Instance: Server URL, Server ID, User ID, User Workspace ID.
  • Relations: Person, the matched standard Person record for this user.
  • Aggregate: Number of Emails with Same Domain, a count of users sharing the same business domain. This field is part of the data model but is not populated by the app's own ingestion or matching logic.
  • Enrichment state: Is Enriched, Tried To Be Enriched, Is Personal Email, Is Twenty.
  • Person enrichment: City, Country, Job Function, Job Title, LinkedIn, Seniority.
  • Company enrichment: Name, Description, Industry, Industries, Employees, Founded Year, Annual Revenue, Funding Latest Stage, Funding Total Amount, Alexa Rank, LinkedIn, Tags, Tech.

Person relation

The standard Person object gains a Self hosting users relation, so you can see every self-hosted sign-up tied to a given person directly from their record.

View and navigation

A Self hosting users table view exposes all of the fields above as columns and is added to the left sidebar for quick access.

Role

The app ships a default role with read, update, and soft-delete permissions over workspace records. Permanent deletion is not granted.

How sign-up data flows in

The app exposes an unauthenticated HTTP endpoint that self-hosted instances post telemetry to:

  • Method: POST
  • Path: /webhook/telemetry

A request is processed only when its action is user_signup; any other event type is acknowledged and ignored. The payload carries the user's email, first and last name, locale, the originating server URL and server ID, and workspace identifiers.

When a valid sign-up arrives:

  1. Non-signup events are acknowledged and ignored first; then sign-ups with no email are skipped; then sign-ups whose email contains test or example (case-insensitive) are ignored.
  2. If a Self Hosting User already exists for that email, it is updated with the latest details; otherwise a new one is created. The app persists Name, Email, Locale, Server URL, Server ID, User ID, and User Workspace ID; Domain and the enrichment fields are left for separate processes.
  3. Whenever a Self Hosting User is created or its email changes, the app looks for a Person with the same email. If one exists it links them; if not, it creates a Person and links it. Records that already have a matched Person and an unchanged email are left untouched.

Configuration

This app requires no server or application variables. To send data into it, point a self-hosted Twenty instance's telemetry at the webhook path above on the workspace where this app is installed.

Limitations

  • The telemetry webhook is intentionally unauthenticated and performs no signature or shared-secret verification. Any client that can reach the endpoint can submit sign-up events, so it should only be exposed to trusted self-hosted instances.
  • Test/example filtering is a simple substring match: any email containing test or example anywhere is excluded, even if it is a legitimate address.
  • The enrichment fields and the Domain field are part of the data model but are populated by separate processes; the app itself ingests sign-ups and performs Person matching.