# Contact Creation in Twenty CRM: How Automated Message Processing Builds Your Contact Database

> Discover how Twenty CRM automates contact creation from messages. Learn about participant detection, background jobs, deduplication, and building your contact database efficiently.

- Repository: [Twenty/twenty](https://github.com/twentyhq/twenty)
- Tags: how-to-guide
- Published: 2026-03-27

---

**Twenty CRM automatically creates contacts from incoming messages through a multi-step pipeline that detects participants, enqueues background jobs, filters out internal users, deduplicates email addresses, and creates or restores both companies and persons within the workspace context.**

Contact creation in Twenty CRM is fully automated for connected email accounts, transforming incoming messages into structured workspace data without manual data entry. When messages are imported, the system evaluates participants against auto-creation policies to determine which contacts should be added to your database. This process leverages a robust queue-based architecture located in `twentyhq/twenty` to ensure reliable, transactional contact management at scale.

## The Contact Creation Pipeline

The entire workflow is orchestrated through three core modules that handle detection, queuing, and persistence. All operations run inside the workspace’s transactional context using `GlobalWorkspaceOrmManager.executeInWorkspaceContext`, ensuring atomicity across person, company, and participant tables.

### Phase 1: Message Ingestion and Participant Detection

The process begins in `MessagingSaveMessagesAndEnqueueContactCreationService.saveMessagesAndEnqueueContactCreation` located at [`packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts). This service parses every message participant and applies the channel’s auto-creation policy to identify candidates.

**Key filtering logic includes:**
- Removing the connected account itself from creation candidates
- Filtering out non-professional email addresses
- Marking remaining participants with `shouldCreateContact = true`

Once candidates are identified, the service pushes a job onto the `contactCreationQueue` containing the workspace ID, connected account, and a list of `{handle, displayName}` objects for the participants that need contacts (lines 61-78).

### Phase 2: Background Job Processing

The `CreateCompanyAndContactJob` located at [`packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts) consumes the queued payload and delegates to the creation service. The job handler processes contacts in batches defined by the `CONTACTS_CREATION_BATCH_SIZE` constant to manage memory and database load efficiently.

The main entry point is `CreateCompanyAndPersonService.createCompaniesAndPeopleAndUpdateParticipants` in [`packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts). This method ensures the connected account’s owner workspace member exists and establishes the workspace’s transactional context before processing begins.

### Phase 3: Data Sanitization and Deduplication

Before creating records, the pipeline applies two critical utilities:

**Filter Internal Users:** The `filterOutContactsThatBelongToSelfOrWorkspaceMembers` utility ([`filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts`](https://github.com/twentyhq/twenty/blob/main/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts)) removes contacts belonging to the connected account itself or any workspace member, preventing duplicate creation for internal users.

**Deduplicate Handles:** The `getUniqueContactsAndHandles` utility ([`get-unique-contacts-and-handles.util.ts`](https://github.com/twentyhq/twenty/blob/main/get-unique-contacts-and-handles.util.ts)) reduces the remaining contacts to a unique set of email handles, ensuring the system does not attempt multiple creations for the same email address within a single batch.

### Phase 4: Entity Resolution and Persistence

The service determines creation needs through `computeContactsThatNeedPersonCreateAndRestoreAndWorkDomainNamesToCreate`:

- **Create:** Handles not found in the database require new `PersonWorkspaceEntity` records
- **Restore:** Handles found on soft-deleted persons require restoration
- **Company Creation:** Work-domain email addresses (`isWorkEmail`) trigger company creation or restoration based on domain names

**Company Resolution:** `CreateCompanyService.createOrRestoreCompanies` (called at line 1110) receives the list of work domain names and returns a map of `{domain → companyId}` for linking contacts to their organizations.

**Person Creation:** For new contacts, `formatPeopleToCreateFromContacts` (line 308) builds `Partial<PersonWorkspaceEntity>` objects containing the email, derived first/last names from the display name, linked `companyId`, and audit fields (`createdBy`). These are persisted via `CreatePersonService.createPeople` (lines 128-130).

**Restoration:** For soft-deleted matches, `formatPeopleToRestoreFromContacts` (line 359) builds restoration DTOs with `{personId, companyId}` passed to `CreatePersonService.restorePeople`.

Existing persons are detected via a query builder that searches for `PersonWorkspaceEntity` records where primary or additional emails match the unique handles (lines 87-96).

## Implementing Contact Creation Programmatically

While the pipeline runs automatically during message import, you can trigger or test the process manually.

### Triggering Contact Creation from Messages

To manually invoke the contact detection and enqueueing logic:

```typescript
import { MessagingSaveMessagesAndEnqueueContactCreationService } from
  'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service';
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';

// Assume we already have `messages`, `channel`, `connectedAccount`, `workspaceId`
await new MessagingSaveMessagesAndEnqueueContactCreationService(
  messageQueueService,
  messageService,
  messageParticipantService,
  messageFolderAssociationService,
  globalWorkspaceOrmManager,
).saveMessagesAndEnqueueContactCreation(
  messages,
  channel,
  connectedAccount,
  workspaceId,
);

```

This call automatically persists messages, determines which participants should become contacts, and pushes a `CreateCompanyAndContactJob` to the queue.

### Direct Job Invocation for Testing

For isolated testing or custom workflows, invoke the job handler directly:

```typescript
import { CreateCompanyAndContactJob } from
  'src/modules/contact-creation-manager/jobs/create-company-and-contact.job';
import { CreateCompanyAndPersonService } from
  'src/modules/contact-creation-manager/services/create-company-and-contact.service';

const job = new CreateCompanyAndContactJob(new CreateCompanyAndPersonService(
  createPersonService,
  createCompanyService,
  globalWorkspaceOrmManager,
  exceptionHandlerService,
));

await job.handle({
  workspaceId: 'workspace-123',
  connectedAccount: myConnectedAccount,
  contactsToCreate: [{ handle: 'john.doe@acme.com', displayName: 'John Doe' }],
  source: 'EMAIL', // FieldActorSource enum value
});

```

### Inspecting Creation Results

The service returns merged data for verification:

```typescript
const result = await createCompanyAndPersonService.createCompaniesAndPeopleAndUpdateParticipants(...);
console.log('Created / restored persons:', result);

```

## Core Service Architecture and File Structure

The following files implement the end-to-end contact creation workflow in Twenty CRM:

| File | Role |
|------|------|
| [`packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts) | Detects participants that need contacts and enqueues the job |
| [`packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts) | Queue consumer that delegates to the creation service |
| [`packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts) | Core logic: batching, filtering, deduplication, company & person creation/restoration |
| [`packages/twenty-server/src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util.ts) | Helper that collapses duplicate email handles |
| [`packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts) | Prevents creating contacts that already belong to the user or workspace members |
| [`packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts) | Creates or restores companies based on work-domain emails |
| [`packages/twenty-server/src/modules/contact-creation-manager/services/create-person.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/services/create-person.service.ts) | Persists new persons or restores soft-deleted ones |
| [`packages/twenty-server/src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant.ts) | Defines how many contacts are processed per batch |

## Summary

- **Automated Detection:** The `MessagingSaveMessagesAndEnqueueContactCreationService` identifies contact candidates from imported messages while filtering out internal accounts and non-professional emails.
- **Queue-Based Processing:** Jobs are enqueued to `CreateCompanyAndContactJob` for background processing, enabling scalable handling of large message volumes.
- **Transactional Safety:** All database operations execute within `GlobalWorkspaceOrmManager.executeInWorkspaceContext`, ensuring atomic creation of related company and person records.
- **Deduplication:** The pipeline filters existing workspace members and deduplicates email handles before database insertion.
- **Smart Restoration:** The system detects soft-deleted persons and restores them rather than creating duplicates, maintaining data lineage.
- **Company Linking:** Work-domain emails automatically trigger company creation or linking, establishing organizational relationships during contact import.

## Frequently Asked Questions

### How does Twenty CRM prevent duplicate contact creation?

The system implements a multi-layer deduplication strategy. First, `filterOutContactsThatBelongToSelfOrWorkspaceMembers` removes the connected account and existing workspace members from the creation list. Then, `getUniqueContactsAndHandles` collapses duplicate email addresses within the batch. Finally, the service queries existing `PersonWorkspaceEntity` records (lines 87-96) to detect active or soft-deleted matches, updating or restoring existing records rather than inserting duplicates.

### What happens when a contact is created from a work email domain?

When the system encounters a work-domain email (determined by `isWorkEmail`), it extracts the domain name and passes it to `CreateCompanyService.createOrRestoreCompanies`. This service either creates a new company record or restores a soft-deleted one, returning a `companyId` that is linked to the new or restored person record during the `formatPeopleToCreateFromContacts` phase.

### Can contact creation be triggered manually outside of email imports?

Yes. While the standard flow triggers from `MessagingSaveMessagesAndEnqueueContactCreationService`, you can directly invoke `CreateCompanyAndContactJob.handle()` with a payload containing `workspaceId`, `connectedAccount`, and `contactsToCreate` array. This is particularly useful for testing, data migration scripts, or integrating custom message sources beyond standard email imports.

### How does the system handle soft-deleted contacts?

During the `computeContactsThatNeedPersonCreateAndRestoreAndWorkDomainNamesToCreate` phase, the service categorizes handles into those requiring creation versus restoration. For contacts matching soft-deleted persons, the system builds restoration DTOs via `formatPeopleToRestoreFromContacts` and calls `CreatePersonService.restorePeople`, reactivating the existing record with updated company associations rather than creating a new database entry.