feat: Add post on twenty

This commit is contained in:
2025-08-03 13:51:18 +02:00
parent 27968f6ae5
commit b0e58f1737
9 changed files with 293 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
name: twenty
services:
server:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- type: bind
source: ./server_local_data
target: /app/packages/twenty-server/.local-storage
ports:
- "3000:3000"
environment:
NODE_PORT: 3000
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DISABLE_DB_MIGRATIONS: ${DISABLE_DB_MIGRATIONS}
DISABLE_CRON_JOBS_REGISTRATION: ${DISABLE_CRON_JOBS_REGISTRATION}
STORAGE_TYPE: ${STORAGE_TYPE}
STORAGE_S3_REGION: ${STORAGE_S3_REGION}
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
labels:
- "traefik.http.middlewares.twenty-add-response-headers.headers.customresponseheaders.Strict-Transport-Security=max-age=31536000; includeSubDomains"
- "traefik.http.middlewares.twenty-add-response-headers.headers.customresponseheaders.Access-Control-Allow-Origin=*"
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.routers.twenty.rule=Host(`twenty.hyteck.de`)"
- "traefik.http.routers.twenty.middlewares=twenty-add-response-headers"
- "traefik.http.routers.twenty.service=twenty-service"
- "traefik.http.routers.twenty.entrypoints=web-secure"
- "traefik.http.routers.twenty.tls=true"
- "traefik.http.routers.twenty.tls.certResolver=default"
- "traefik.http.services.twenty-service.loadbalancer.server.port=3000"
depends_on:
db:
condition: service_healthy
healthcheck:
test: curl --fail http://localhost:3000/healthz
interval: 5s
timeout: 5s
retries: 20
restart: always
networks:
- traefik
- default
worker:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- type: bind
source: ./server_local_data
target: /app/packages/twenty-server/.local-storage
command: [ "yarn", "worker:prod" ]
environment:
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DISABLE_DB_MIGRATIONS: "true" # it already runs on the server
DISABLE_CRON_JOBS_REGISTRATION: "true" # it already runs on the server
STORAGE_TYPE: ${STORAGE_TYPE}
STORAGE_S3_REGION: ${STORAGE_S3_REGION}
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
depends_on:
db:
condition: service_healthy
server:
condition: service_healthy
restart: always
networks:
- default
db:
image: postgres:16
volumes:
- type: bind
source: ./db_data
target: /var/lib/postgresql/data
environment:
POSTGRES_USER: ${PG_DATABASE_USER:-postgres}
POSTGRES_PASSWORD: ${PG_DATABASE_PASSWORD:-postgres}
healthcheck:
test: pg_isready -U ${PG_DATABASE_USER:-postgres} -h localhost -d postgres
interval: 5s
timeout: 5s
retries: 10
restart: always
redis:
image: redis
restart: always
command: [ "--maxmemory-policy", "noeviction" ]
networks:
traefik:
name: "traefik"
external: true

View File

@@ -0,0 +1,19 @@
TAG=latest
#PG_DATABASE_USER=postgres
# Use openssl rand -base64 32
PG_DATABASE_PASSWORD=
#PG_DATABASE_HOST=db
#PG_DATABASE_PORT=5432
#REDIS_URL=redis://redis:6379
SERVER_URL=https://twenty.hyteck.de
# Use openssl rand -base64 32
APP_SECRET=
STORAGE_TYPE=local
# STORAGE_S3_REGION=eu-west3
# STORAGE_S3_NAME=my-bucket
# STORAGE_S3_ENDPOINT=

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -0,0 +1,169 @@
---
title: "Trying Twenty: How does an Open Source CRM work?"
date: 2025-08-03T06:10:10+02:00
lastmod: 2025-08-03T12:10:10+02:00
draft: false
image: "uploads/twenty.png"
categories: ['English']
tags: ['crm', 'twenty', 'salesforce', 'django', 'self-hosting']
---
As some of you might know, I spend my day working with Salesforce, a very, very feature-rich CR that you pay big money to use.
Salesforce is the opposite of OpenSource and the many features are expensive. Salesforce business model is based on this and on the lock-in effect.
If your company invested in implementing Salesforce, they'll likely pay a lot to keep it.
So what does an alternative look like? Let's have a look at [Twenty](https://twenty.com), an OpenSource CRM that recently reached the magic 1.0 version.
# Getting started
There are two options of getting started: Register at [app.twenty.com](https://app.twenty.com) and start right away on the devs instance or self-host Twenty on your own server.
I did the ladder, so let's discuss how that. The basic steps I took were
* point twenty.hyteck.de to a server
* Install traefik on the server (I cheated, traefik was already installed)
* Deploy [this docker-compose.yml](docker-compose.yml) with [this env file](env)
Then visit the domain and set up the first user.
# Features
Twenty offers an initial datamodel that you should be familiar from other CRMs. the standards objects are
![A screenshot of the person model in Twenty](person-model.png)
* **Persons** A individual person. You can attach notes, E-Mails, etc..
* **Companies** The same for organizations. Organization websites must be unique
* **Opportunities** The classic opportunity with customizable stages
* **Notes** They can be attached to any of the objects above
* **Tasks** Items to work on
* **Workflows** Automations similar to Salesforce flows. E.g. you can create a task every time an Opportunity is created.
The basic datamodel can be extended in the GUI. Here is how my "Company" model looks like
![A screenshot of twenty. It shows the company model being renamed to Organizations and deactivated fields such as Twitter links or number of employees.](organization_dm.png)
You can add any of the following fields to an object.
![A list of fields: Text, Number, True/False, Date and Time, Date, Select, Multi-Select, Rating, Currency, E-Mails, Links, Phones, Full Name, Address, Relation and the Advanced fields called Unique ID, JSON and Array](fields.png)
### Workflows
Workflows are Twenty's way of allowing users to build automations. You can start a Workflow when a Record is created,
updated or deleted. In addition, they can be started manually, on a schedule and via Webhook (yeah!).
![A workflow in twenty. After the Trigger "Organization" created there is a new task generated, a webhook send and a form used.](workflow1.png)
You can then add nodes that trigger actions. Available right now are
* **Creating, updating or deleting a record**
* **Searching records**
* **Sending E-Mails** This is the only option to trigger e-mails so far
* **Code** Serverless Javascript functions
* **Form** The form will pop up on the user's screen when the workflow is launched from a manual trigger. For other types of triggers, it will be displayed in the Workflow run record page.
* **HTTP request** Although possible via Code, this is a handy shortcut to trigger HTTP requests
What is currently completely missing are Foreach-loops and [conditions](https://github.com/twentyhq/core-team-issues/issues/1265). I can not say "If Opportunity stage is updated to X do Y else, do Z".
Without this, Workflows are really limited in their power.
What already seems quite mature though is the code option. It allows to put in arbitrary code and output a result.
![Screenshot of a javascript function in Twenty that adds two numbers together](serverless_function.png)
I did not try a lot, but I assume most basic Javascript works. I successfully built an http request that send data to a server.
If what you're doing is straightforward enough to not use loops and conditions or if oyu are okay with doing all of them in the Code node, you can do basically anything.
## API
Twenty offers an extensive API that allows you to basically do everything. It's well documented and easy to use.
Here is an example of me, syncing Rescue Organizations from [notfellchen.org](https://notfellchen.org) to Twenty.
```python
import requests
from fellchensammlung.models import RescueOrganization
def sync_rescue_org_to_twenty(rescue_org: RescueOrganization, base_url, token: str):
if rescue_org.twenty_id:
update = True
else:
update = False
payload = {
"eMails": {
"primaryEmail": rescue_org.email,
"additionalEmails": None
},
"domainName": {
"primaryLinkLabel": rescue_org.website,
"primaryLinkUrl": rescue_org.website,
"additionalLinks": []
},
"name": rescue_org.name,
}
if rescue_org.location:
payload["address"] = {
"addressStreet1": f"{rescue_org.location.street} {rescue_org.location.housenumber}",
"addressCity": rescue_org.location.city,
"addressPostcode": rescue_org.location.postcode,
"addressCountry": rescue_org.location.countrycode,
"addressLat": rescue_org.location.latitude,
"addressLng": rescue_org.location.longitude,
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
}
if update:
url = f"{base_url}/rest/companies/{rescue_org.twenty_id}"
response = requests.patch(url, json=payload, headers=headers)
assert response.status_code == 200
else:
url = f"{base_url}/rest/companies"
response = requests.post(url, json=payload, headers=headers)
assert response.status_code == 201
rescue_org.twenty_id = response.json()["data"]["createCompany"]["id"]
rescue_org.save()
```
#
# The Company, Business Model and Paid Features
The company behind Twenty is called "Twenty.com PBC" and mostly seems to consist of former AirBnB employees in Paris.
The company is probably backed by Venture Capital.
The current business model is to charge for using the company's instance of Twenty. It starts at 9\$/user/month without
enterprise features. SSO and support will cost you 19\$/user/month.
Selfhosting is free but SSO is locked behind an enterprise badge with seemingly no way to pay for activating it.
I suspect that in the future more features will become "Enterprise only" even when self-hosting. All contributors must agree
to [a Contributor License Agreement (CLA)](https://github.com/twentyhq/twenty/blob/main/.github/CLA.md), therefore I
believe they could change the License in the future, including switching away from Open Source.
# Conclusion
Twenty is a really promising start of building a good CRM. The ease of customizing the datamodel,
using the API and a solid beginning to Flows allows users to get a lot of value from it already.
Flows need some more work to become as powerful as they should be and the E-Mail integration needs to get better.
Stating the obvious: This is not something that could ever replace Salesforce. But it doesn't have to!
There are many organizations that would benefit a lot from a CRM like Twenty, they simply don't need, can't handle or
don't want to pay for all the features other CRMs offer.
If Twenty continues to focus on small to medium companies and the right mix of standard features vs. custom development options I see a bright future for it.
There are the usual problems of VC-backed OSS development, we shall see how it goes for them.
# Addendum: Important Features
Here is a short list of features I missed and their place on the roadmap if they have one
* **Compose & Send E-Mails** Planned [Q4 2025](https://github.com/orgs/twentyhq/projects/1?pane=issue&itemId=106097937&issue=twentyhq%7Ccore-team-issues%7C811)
* **Foreach loops in Workflows** [Q3 2025](https://github.com/orgs/twentyhq/projects/1/views/33?pane=issue&itemId=93150024&issue=twentyhq%7Ccore-team-issues%7C21)
* **Conditions in Flows** [Q4 2025](https://github.com/orgs/twentyhq/projects/1/views/33?pane=issue&itemId=121287765&issue=twentyhq%7Ccore-team-issues%7C1265)

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

BIN
static/uploads/twenty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB