The short answer
Pick Twenty if you are a small sales team that wants a modern CRM and nothing else: it looks and works like a spreadsheet with boards, it has custom fields and objects, workflows, dashboards, an API and webhooks in the free version, and it reads each seller's mailbox over IMAP. It needs about 1.5 GB of memory at rest, and "only see your own deals" is in its paid plan. Pick Odoo if the CRM is a step towards quotes and invoices: its CRM is solid and free, it limits each seller to their own deals at no cost, it validates Swedish VAT numbers and a deal becomes a quote and an invoice in Swedish kronor. It idles in about 250 MB, but custom fields need developer mode, the Swedish translation has gaps, and Studio, the mobile app and several other apps are in the paid Enterprise edition.
What we tested
We installed both on the same fresh Melonslab VC-P Titanium with Debian 13, one after the other, each under a user of its own behind the same Caddy, by following our guides Twenty with rootless Podman and Odoo Community with rootless Podman step by step. Every step worked as written. In Odoo we installed the guide's CRM, Sales and Invoicing, nothing else.
Then we gave both the same data:
- 50 Swedish companies with names such as Björkvägen Bygg AB and Åsen Mekaniska AB, addresses in Örebro, Växjö and Luleå, phone numbers, an organisation number and a VAT number each; 100 contacts at them, such as Åsa Öberg and Örjan Wästberg; and 30 deals in SEK spread over seven stages, four of them lost.
- We built these as CSV files laid out like HubSpot's export ("all properties and associations", with
Record IDfirst and the associated company's ID and name), and imported them with each CRM's own importer. The files are constructed by us, not exported from a real HubSpot account. A second, smaller set of 5 accounts, 10 contacts and 5 opportunities used Salesforce's object and field names (Account,Contact,Opportunity;Id,AccountId,StageName,CloseDate), also constructed. - Five tasks or activities and five notes on the deals, in Swedish.
- Two users: Anna, the sales manager and admin, and Sara, a sales rep who may change deals but not delete them, and in Twenty not change the amount.
- A test mail server on the same machine for IMAP and SMTP. No real email was sent anywhere.
We then tested email, automation, webhooks, reports, export, Swedish, and deleting one contact everywhere, rebooted the server, measured, and ran each guide's backup.
The numbers
| Twenty 2.46.0 | Odoo 20.0 Community | |
|---|---|---|
| Containers | 4: Twenty server, Twenty worker, PostgreSQL 16, Valkey 9 | 2: Odoo, PostgreSQL 18 |
| Memory at rest, 15 minutes after a reboot | 1.49 GB (server 716 MB, worker 751 MB) | 244 MB (Odoo 231 MB) |
| Memory after an afternoon of use | 1.77 GB | 1.32 GB (four workers) |
| Images on disk | 1.80 GB | 2.85 GB |
| Data after the test | 109 MB (database 80 MB, files 29 MB) | 192 MB (database 169 MB, files 23 MB) |
From useradd to the login page | about 8 minutes, 2 of them a restart | about 4.5 minutes, CRM, Sales and Invoicing installed |
| Import from HubSpot/Salesforce CSV | per object, with a matching screen; links by domain or a unique field | per object; links by the old ID, no extra columns |
| Custom fields | in the settings, or the API | developer mode, plus a view in XML to show them; drag-and-drop is Enterprise (Studio) |
| "Own deals only" for a seller | paid Organization plan | free |
| Workflows and webhooks | free | free (Automation Rules) |
| Reads each seller's mailbox | yes, IMAP/SMTP, Gmail, Microsoft 365 | no; shared addresses fetched over IMAP |
| GDPR: find a person everywhere | no tool; delete the record | Privacy Lookup lists the records, deletes and logs |
| Backup in our test | 34 MB, three files | 6.8 MB, one ZIP and the config |
| Licence | AGPL 3.0, some files under a commercial licence | LGPL 3.0; Enterprise is proprietary |
Memory is the sum of each app's containers from podman stats. Twenty used 2.36 GB four minutes after the reboot and settled at 1.49 GB after about ten more. Odoo was at 239 MB after four minutes and stayed there until people used it; its four workers then grow, and are restarted when one passes 600 MB, as its guide sets up. Twenty's 29 MB of files were the pictures of its app catalogue: we attached no files.
Setup
Both guides worked as written. Twenty downloaded its images and set up its database in 4 minutes 35 seconds, then needs a restart of about 2 minutes 20 seconds once you change its address from the SSH tunnel to the real one. The first account is made through the tunnel, as its guide explains. Odoo took 4 minutes 26 seconds from useradd to a working login page with CRM, Sales and Invoicing installed for a Swedish company, which brought the Swedish chart of accounts, SEK and the VAT rates with it.
Odoo's guide then has a few more things to switch off: the database manager, free customer sign-up, Lead Enrichment and Partner Autocomplete, which send data to Odoo S.A. Twenty's guide switches off its telemetry.
Importing from HubSpot and Salesforce
Neither tool connects to HubSpot or Salesforce. Both import one CSV file per object, so you export companies, contacts and deals separately and import them in that order. Neither imported owners from our files, and neither brings email history, notes or activities across from a CSV of companies and deals.
Twenty has an import screen for each object, under the ⋮ menu at the top right of the list: upload, match columns, check, confirm. It matched 5 of our 13 HubSpot company columns by itself (city, industry, the creation date, and our own organisation and VAT fields by their names), and we matched the rest by hand, such as the street, postcode and country into the parts of Address. 50 companies took 8 seconds. Things we ran into:
- Companies have no phone field until you add one. We added it under Settings > Data model, and the import matched it.
- A contact's company can only be found by Twenty's own ID, the company's domain, or a field you have marked unique. Not by name, and not by HubSpot's ID. For HubSpot we added a Company Domain column to the contacts and deals in a spreadsheet, looked up from the company file. For Salesforce we gave companies a unique field, Old CRM ID, filled it with the account
Id, and then matched each contact'sAccountIdto it. All 110 contacts and 35 deals ended up on the right company. - Twenty matched Salesforce's
Idcolumn to its own ID, and the check step stopped every row with "ID is not a valid UUID". Set that column to Do not import. - For the deal stage, Twenty shows a list of the values in the file and lets you match each to a stage, for example Contract Sent to Proposal. Twenty has no stage for lost deals; we added Lost to the stage field first.
- Close date is a date with a time in Twenty, so
2026-12-15showed as "Dec 15, 2026 1:00 AM" in Swedish time.
Odoo imports from ⚙ > Import records in any list, with a Test button that checks the file before anything is saved. We ran its importer through its own functions, the same ones the screen uses. It matched fewer columns by name (city, industry, email and our organisation number field), and matched HubSpot's Phone Number to a search field instead of the phone. What stood out:
- IDs from the old system work. Matched to External ID, HubSpot's
Record IDand Salesforce'sIdbecame Odoo's own references, so contacts found their company throughAssociated Company IDsorAccountId, and deals through the same, without extra columns. This was the smoothest part of either import. - Odoo has one name field for a person. We added a column with first and last name together.
- HubSpot's file does not say that a company is a company, so we imported them and then set Company on all 55 at once in the list.
- Values that do not exist, such as an industry, can be created during the import. Stages have no matching step: we renamed the HubSpot and Salesforce stages to Odoo's in the file, and marked the four lost deals as lost afterwards, which in Odoo is not a stage but an archived deal with a reason.
- With Leads switched on, as in Odoo's guide, imported deals become leads. We turned them into opportunities in one go; importing from the pipeline view sets that for you.
Pipeline and day-to-day work
Both show deals as a board with a column per stage and the sum in each, and you drag deals between stages.
Twenty works like a spreadsheet: every list is a table you filter, sort, group and save as views, and By Stage shows the board. Records open on the side with their timeline, tasks, notes, files and emails. Tasks have an assignee and a due date, and notes are rich text. Company logos are fetched from Twenty's own service by domain, as its guide explains.
Odoo's pipeline shows each deal's amount, customer, activities and stars, and opens to a form with the history ("chatter") underneath, where you log notes, schedule calls, meetings or to-dos for someone, and send emails. Won and Lost are buttons, and lost deals get a reason. The pipeline opens filtered on My Opportunities. From a deal, New Quotation starts a quote with your products and Swedish VAT, which becomes an order and an invoice, as the Odoo guide shows.
Custom fields and objects
Twenty: Settings > Data model adds fields of many types (text, number, currency, select, date, phones, relations and more) to any object, and creates new objects, with no limit. We added organisation number, VAT number, industry, phone and Old CRM ID to companies through the API in seconds, and they showed up in lists, filters, the importer and the export.
Odoo Community has no point-and-click way to add a field to a form. With developer mode on, Settings > Technical > Fields creates the field (it gets an x_ name), and to show it on the contact form you write a small inherited view in XML under Technical > Views. We did both for the organisation number and it worked, but this is not something a sales manager will do. The drag-and-drop editor, Studio, is in Odoo's paid Enterprise edition.
What each does with a mailbox is the clearest difference between them.
Twenty connects each user's own mailbox: Gmail or Microsoft 365, which need an app registered with Google or Microsoft, or any mail server over IMAP and SMTP, under Settings > Accounts > Connect via IMAP/SMTP. We connected Anna's test mailbox, which held three emails: two from contacts in the CRM and one from an unknown sender. Twenty imported the two from known contacts and showed them on Erik's and Åke's records; it left the unknown sender out, because by default it creates contacts only for people you send email to. Also by default, other users see only the date and the participants of your emails, not the subject or the text; you choose under the account's settings. Things to know:
- Twenty refuses mail servers on private addresses, as protection against misuse. A mail server on your own network has to be named in the setting
OUTBOUND_HTTP_ALLOWED_INTERNAL_HOSTSon both the server and the worker. Addresses in169.254.0.0/16, such ashost.containers.internalin Podman, are never allowed. - Our Sales rep role, with every settings permission off, also hid Accounts, so Sara could not connect her own mailbox. Give the role that permission if your sellers should.
- We did not send email from Twenty's records.
Odoo does not read anyone's personal inbox. It sends email from the deal's history, and fetches shared addresses over IMAP every few minutes. We set up an outgoing server and an incoming one on the test mail server. A quote email sent from a deal reached the contact, and his reply landed back on the same deal's history. An email to the sales team's address, info@ by default, became a new lead with the sender and text. An email to the general reply address that matched no deal was returned to the sender. Your mail provider has to deliver those addresses to a mailbox Odoo can fetch.
Automation, API and webhooks
Twenty has Workflows in the free version, with triggers when a record is created, updated or deleted, manually, on a schedule, or by a webhook, and steps such as create or update a record, send an email, an HTTP request, code, a form, if/else and a delay. We built "when a deal is created, create the task Ring kunden om den nya affären" in a few clicks, and it ran. A webhook under Settings > MCP & APIs posted the whole new deal as JSON within seconds, and each webhook gets a secret to sign with. The API is REST and GraphQL, with keys that have a role of their own, and it can also change the data model; we created all our custom fields through it.
Odoo has Automation Rules once you install the free app of that name. We made a rule that, when a deal is created, schedules a call for its salesperson and sends a webhook. Both worked; by default the webhook only says which record it was, and you choose fields to send more. The API is JSON-RPC and XML-RPC with a user's password or API key, and we used it for most of this test. Odoo's pricing page (read 14 October 2026) lists "External API" as part of its paid Custom plan, which is about Odoo's own hosting; on your own server, Community's API is there.
Reports
Twenty has Dashboards with charts built from any object: a bar chart of the deal amounts per stage took three clicks. Odoo's CRM > Reporting shows the pipeline as a graph, a pivot table or a list, grouped by stage, salesperson, month or any field, and exports to a spreadsheet. Odoo's forecast and activity reports are there too. Both did what a small team needs; neither has the ready-made forecasting and attribution reports the big services sell.
Permissions
We gave Sara, the sales rep, less than Anna.
Twenty: under Settings > Members > Roles we made Sales rep, which may see and edit all records but not delete or destroy them, may not touch settings, and may see but not change the Amount of a deal. Twenty enforced it on the server: changing a deal's name worked, but changing the amount or deleting it answered "User does not have permission". What Twenty cannot do in the free version is limit Sara to her own deals: Record-level permissions say "This feature is part of the Organization plan".
Odoo: Sara got Sales: User: Own Documents Only. She saw 12 of the 31 open deals, the ones that were hers, got "Uh-oh! Looks like you have stumbled upon some top-secret records" when she tried to open one of Anna's, could not delete her own deals, and could not open the settings. All in Community, without paying.
Swedish
Twenty comes in Swedish: each user picks Swedish under Settings > Experience. Menus and the standard objects change, so Companies becomes Företag and Opportunities Affärsmöjligheter. Dates and numbers follow separate format settings that start from the browser; with an English browser Sara saw "dec. 15, 2026 1:00 fm" until she changed them.
Odoo is mostly in Swedish once the language is added, as its guide shows, with some English left, such as Pipeline, My Opportunities and Generate in the CRM. Amounts show as "235 000,00 kr".
Our names with å, ä and ö went through both importers, searches and exports unchanged. SEK is the company currency in Odoo, set by the Swedish localisation; in Twenty each amount carries its currency, and our SEK amounts showed with "kr". Neither has a field for a Swedish organisation number on customers; we added one in both. Odoo checks VAT numbers: it refused SE123456789001 with "does not seem to be valid" and showed the expected format, and accepted the valid ones. Twenty stores whatever you type.
GDPR: deleting a contact everywhere
We deleted Erik Lindqvist, who had emails, a deal and history in both, and then searched each database dump for his email address.
Twenty has no tool to find a person across the CRM. Deleting him in the browser moves the record to Deleted, from where you destroy it; through the API, our delete removed the row at once. Afterwards his name and address were still in two places: the synced email he had sent, as its sender, and the log of a workflow Twenty runs by itself when a person is created, which keeps a copy of the record. You have to remove the email from the mailbox side and clean the workflow runs yourself.
Odoo has Privacy Lookup, in Community: give it a name and email address, and it lists every record that holds them. For Erik it found his contact, his email reply and the deal where he was the contact person. It deleted the contact and the email in one click each and wrote an anonymised log ("E*** L*******"). It does not change the deal, which kept his name, email and phone in its own fields, and the deal's history kept the earlier change that set him as its contact. Both need clearing by hand.
HubSpot, for comparison, deletes one contact at a time, purges within up to 30 days and blocks the address from being added again, according to its help article (read 14 October 2026).
Export and leaving
Twenty exports any view to CSV from the ⋮ menu, with every field, including your own, in UTF-8 that Excel opens with å, ä and ö intact. Odoo exports any list to CSV or Excel with the fields you choose, and the old IDs we imported came back out as external IDs. Both store everything in PostgreSQL, and both guides back up the whole database.
Resources
Twenty is two Node.js processes, the server and the worker, that keep a lot in memory: about 1.5 GB at rest, more right after a start. Odoo is Python with a worker per process, and at rest it is small; its guide's four workers grow with use and are recycled. On a VC-P Alloy with 8 GB, either runs with plenty of room, and both together used about 3.1 GB at their busiest in our test.
Updates and backups
Twenty's guide follows the v2 tag and updates itself with podman auto-update; Twenty upgrades its database when it starts. Its backup is a database dump, a volume and the encryption key, 34 MB here.
Odoo's guide pins a weekly build and updates by hand, with a database upgrade each time. A new major version comes each autumn, and moving to it is the hard part: Odoo S.A.'s upgrade service is for Enterprise, and Community relies on the volunteer OpenUpgrade project, as the Odoo guide explains. Its backup is one ZIP with the database and the files, 6.8 MB here.
We ran both backups. Both apps came back by themselves after the reboot.
Licence and cost
We read the licences and pricing pages on 14 October 2026.
- Twenty is open source under the AGPL 3.0, according to its licence file, except files marked
@license Enterprise, which are under a commercial licence and need a subscription in production. Twenty.com PBC sells hosted Twenty at 9 US dollars per user a month for Pro and 19 for Organization, which adds row-level permissions, SAML and OIDC single sign-on, audit logs and a custom domain, and Enterprise from 50,000 dollars a year, according to its pricing page. On your own server, the Organization features need a key from Twenty; its documentation does not state a price for it. Everything we tested here, including workflows, dashboards, roles with field permissions, the API and webhooks, worked without one. - Odoo Community is open source under the LGPL 3.0, and Enterprise needs a subscription, according to Odoo's licence page. Odoo's pricing page showed One App Free at 0, Standard at 24.90 euros and Custom at 44.90 euros per user a month, with a discount for the first 12 months; only Custom may run on your own server. The Community image we ran has no Studio, VoIP, Marketing Automation, Sign, Documents, Helpdesk, mobile app module, WhatsApp, spreadsheet editing or accounting reports; those are Enterprise.
For a company running its own CRM, both cost nothing but the server.
HubSpot and Salesforce today
What follows is from HubSpot's and Salesforce's own pages, read on 14 October 2026, not from our tests.
HubSpot: its pricing page, where the Sales Hub page now leads, showed Free at 0 euros for up to 2 users, Starter at 7 euros per seat a month (marked "Limited-time · 65% off") for up to 10 users, Professional at 90 and Enterprise at 175 euros per seat a month, billed yearly. According to its product and services catalogue, the free tools allow one pipeline per object, 10 custom properties in total, 10 dashboards and up to 1,000 contacts; paid editions allow 1,000 custom properties per object and 15 (Starter) or 100 (Professional) pipelines; sales automation goes from "Up to 50 workflows with restricted triggers and actions" in Starter to 300 in Professional and 1,000 in Enterprise. "Control which data users can access and edit" is listed under Enterprise on the pricing page. HubSpot exports records per object as CSV or Excel, with Record ID first and a column per associated object, according to its export article.
Salesforce: its Swedish pricing page listed Sales Cloud as Free Suite at 0 kr, Starter Suite at 250 kr (billed monthly or yearly), Pro Suite at 1,000 kr, Core at 1,950 kr, Advanced at 3,950 kr and Max at 5,500 kr per user a month, billed yearly. API access is included from the Enterprise edition up; Professional has to buy it, according to Salesforce's help. Its data export gives a ZIP of CSV files, weekly in Enterprise, Performance and Unlimited and every 29 days in the others, according to Trailhead.
What you give up compared with HubSpot and Salesforce
On your own server the customer data stays in Sweden, and the cost is a server instead of a price per seat: five sellers on HubSpot Professional are 450 euros a month at the prices above, and on Salesforce Pro Suite 5,000 kr. But be honest about what you leave behind:
- Sales tools. Email tracking, sequences, meeting booking, calling and the AI assistants and agents both services build in. Twenty has AI features that need a key from an AI provider, which we did not set up; Odoo's VoIP is Enterprise.
- The ecosystem. Both services connect to hundreds of apps out of the box. Here, integrations are the API, webhooks, Twenty's workflows or Odoo's apps, and what you build.
- Phones. Odoo's mobile app is Enterprise. Twenty has no mobile app; its web app works in a phone's browser.
- The migration. There is no importer that reads your HubSpot or Salesforce account. You move CSV files per object, prepare columns in a spreadsheet as above, and lose the email and activity history unless you script it through the API. Keep the old account until you have checked the result.
- Running it. Updates, backups, the server and email delivery are yours, and for Odoo, a major upgrade every few years that the Community edition has to manage without Odoo's own service.
Which one to pick
A small sales team that wants a modern CRM: take Twenty. It is the closer match to HubSpot's way of working, with views, boards, custom fields and objects, workflows, dashboards, an API and webhooks free, each seller's mailbox on the contact's timeline, and a clean Swedish interface. Its weak points are memory (about 1.5 GB at rest), "own deals only" and single sign-on behind a paid key, an importer that cannot link contacts to companies by name or old ID, close dates with a time, and no tool for finding a person everywhere. Follow the Twenty guide; it runs comfortably on a VC-P Alloy.
A company that also wants quotes, invoicing or more of an ERP: take Odoo Community. Its CRM is free and complete for a small team, sellers can be limited to their own deals, imports keep the old system's IDs, Swedish VAT numbers are checked, Privacy Lookup helps with GDPR requests, and a won deal becomes a quote and an invoice in kronor with Swedish VAT. Its weak points are custom fields that need developer mode and XML, a Swedish translation with gaps, shared mailboxes instead of each seller's inbox, Studio and the mobile app only in Enterprise, and major upgrades without Odoo's help. Follow the Odoo guide, on a VC-P Alloy. If accounting matters most, also look at ERPNext.
Both together fit on one VC-P Alloy: at their busiest they used about 3.1 GB together on our test server, a VC-P Titanium.