Files
twenty/packages/twenty-apps/internal/twenty-for-twenty
Charles Bochet eb1ca1b9ec perf(sdk): split twenty-sdk barrel into per-purpose subpaths to cut logic-function bundle ~700x (#19834)
## Summary

Logic-function bundles produced by the twenty-sdk CLI were ~1.18 MB even
for a one-line handler. Root cause: the SDK shipped as a single bundled
barrel (`twenty-sdk` → `dist/index.mjs`) that co-mingled server-side
definition factories with the front-component runtime, validation (zod),
and React. With no `\"sideEffects\"` declaration on the SDK package,
esbuild had to assume every module-level statement could have side
effects and refused to drop unused code.

This PR restructures the SDK so consumers' bundlers can tree-shake at
the leaf level:

- **Reorganized SDK source.** All server-side definition factories now
  live under `src/sdk/define/` (agents, application, fields,
  logic-functions, objects, page-layouts, roles, skills, views,
  navigation-menu-items, etc.). All front-component runtime
  (components, hooks, host APIs, command primitives) lives under
  `src/sdk/front-component/`. The legacy bare `src/sdk/index.ts` is
  removed; the bare `twenty-sdk` entry no longer exists.

- **Split the build configs by purpose / runtime env.** Replaced
  `vite.config.sdk.ts` with two purpose-specific configs:
  - `vite.config.define.ts` — node target, externals from package
    `dependencies`, emits to `dist/define/**`
  - `vite.config.front-component.ts` — browser/React target, emits to
    `dist/front-component/**`
  Both use `preserveModules: true` so each leaf ships as its own `.mjs`.

- **\`\"sideEffects\": false\`** on `twenty-sdk` so esbuild can drop
  unreferenced re-exports.

- **\`package.json\` exports + \`typesVersions\`** updated: dropped the
bare \`.\` entry, added \`./front-component\`, and pointed \`./define\`
  at the new per-module dist layout.

- **Migrated every internal/example/community app** to the new subpath
  imports (`twenty-sdk/define`, `twenty-sdk/front-component`,
  `twenty-sdk/ui`).

- **Added \`bundle-investigation\` internal app** that reproduces the
  bundle bloat and demonstrates the fix.

- Cleaned up dead \`twenty-sdk/dist/sdk/...\` references in the
  front-component story builder, the call-recording app, and the SDK
  tsconfig.

## Bundle size impact

Measured with esbuild using the same options as the SDK CLI
(\`packages/twenty-apps/internal/bundle-investigation\`):

| Variant | Imports | Before | After |
| ----------------------- |
------------------------------------------------------- | ---------- |
--------- |
| \`01-bare\` | \`defineLogicFunction\` from \`twenty-sdk/define\` |
1177 KB | **1.6 KB** |
| \`02-with-sdk-client\` | + \`CoreApiClient\` from
\`twenty-client-sdk/core\` | 1177 KB | **1.9 KB** |
| \`03-fetch-issues\` | + GitHub GraphQL fetch + JWT signing + 2
mutations | 1181 KB | **5.8 KB** |
| \`05-via-define-subpath\` | same as \`01\`, via the public subpath |
1177 KB | **1.7 KB** |

That's a ~735× reduction on the bare baseline. Knock-on benefits for
Lambda warm + cold starts, S3 upload size, and \`/tmp\` disk usage in
warm containers.

## Test plan

- [x] \`npx nx run twenty-sdk:build\` succeeds
- [x] \`npx nx run twenty-sdk:typecheck\` passes
- [x] \`npx nx run twenty-sdk:test:unit\` passes (31 files / 257 tests)
- [x] \`npx nx run-many -t typecheck
--projects=twenty-front,twenty-server,twenty-front-component-renderer,twenty-sdk,twenty-shared,bundle-investigation\`
passes
- [x] \`node
packages/twenty-apps/internal/bundle-investigation/scripts/build-variants.mjs\`
produces the sizes above
- [ ] CI green

Made with [Cursor](https://cursor.com)
2026-04-18 19:38:34 +02:00
..
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00
2026-04-17 17:29:09 +00:00

This is a Twenty application bootstrapped with create-twenty-app.

Overview

Twenty for Twenty is the official internal Twenty app. It is organized into modules, each integrating a third-party service with Twenty.

Resend module (src/modules/resend/)

Two-way sync between Twenty and the Resend email platform. The module syncs contacts, segments, templates, broadcasts, and emails.

Inbound (Resend -> Twenty):

  • A cron job runs every 5 minutes to pull all entities from the Resend API
  • A webhook endpoint receives real-time events for contacts and emails

Outbound (Twenty -> Resend):

  • Database event triggers push contact and segment changes back to Resend when records are created, updated, or deleted in Twenty

Getting Started

1. Install and run the app

yarn twenty dev

This registers the app with your local Twenty instance at http://localhost:3000/settings/applications.

2. Configure app variables

In Twenty, go to Settings > Applications > Twenty for Twenty and set:

  • RESEND_API_KEY -- Your Resend API key. Create one at https://resend.com/api-keys (full access recommended).
  • RESEND_WEBHOOK_SECRET -- The signing secret for verifying inbound webhooks (see "Webhook setup" below).

3. Webhook setup

The app exposes an HTTP endpoint at /s/webhook/resend that receives Resend webhook events. To connect it:

  1. Go to https://resend.com/webhooks
  2. Click Add webhook
  3. Set the Endpoint URL to your Twenty server's public URL + /s/webhook/resend (e.g. https://your-domain.com/s/webhook/resend)
  4. Set Events types to All Events
  5. Click Add
  6. Copy the signing secret Resend displays and paste it into the RESEND_WEBHOOK_SECRET app variable in Twenty

The webhook handles:

  • Contact events (contact.created, contact.updated, contact.deleted) -- upserts/deletes Resend contact records in Twenty
  • Email events (email.sent, email.delivered, email.bounced, email.opened, email.clicked, etc.) -- updates delivery status on Resend email records in real-time
  • Domain events -- logged and skipped (no domain object in the app yet)

4. Testing webhooks locally

Install the Resend CLI:

brew install resend/cli/resend

Or via npm if Homebrew has issues:

npm install -g resend-cli

Authenticate:

resend login

Start the webhook listener with forwarding to your local Twenty server:

resend webhooks listen --forward-to http://localhost:3000/s/webhook/resend

The CLI will:

  1. Create a public tunnel automatically
  2. Register a temporary webhook in Resend pointing to that tunnel
  3. Forward incoming events (with Svix signature headers) to your local Twenty server
  4. Display events in the terminal as they arrive
  5. Clean up the temporary webhook when you press Ctrl+C

To trigger test events, create or update a contact in the Resend dashboard, or send a test email.

Sync behavior

Inbound sync

Source Mechanism Entities
Cron (every 5 min) Polls Resend API, upserts into Twenty Contacts, segments, templates, broadcasts, emails
Webhook (real-time) Receives Resend events via HTTP Contacts, emails

Outbound sync

Twenty action Resend API call
Create contact contacts.create() -- writes resendId back to Twenty
Update contact (name, email, unsubscribed) contacts.update()
Delete contact contacts.remove()
Create segment segments.create() -- writes resendId back to Twenty
Delete segment segments.remove()

Loop prevention

A lastSyncedFromResend field on contact, segment, and email records tracks when data came from Resend. Outbound triggers skip processing when this field is part of the update, preventing infinite echo loops between inbound and outbound sync.

Commands

Run yarn twenty help to list all available commands.

Learn More