---
title: "Contacts & CRM"
description: "Import contacts, custom fields, categories, deals, and pipelines."
icon: Users
---

Your contacts are the people you reach out to, and the CRM is where you track what happens after they reply. This guide covers getting contacts into Warmbly, enriching them with custom fields and categories, and managing deals across pipelines.

## Importing contacts

Warmbly gives you two ways to bring contacts in: a file upload (CSV, TSV, or XLSX) and an on-demand Google Sheets sync. Both share the same column-mapping screen, so once you learn one, you know the other.

### From a CSV or spreadsheet file

Open the import wizard from the Contacts page (or from a campaign's Leads tab, where imported contacts are attached to that campaign automatically). The wizard walks through four steps:

1. **Upload.** Drag and drop a file or browse for one. Accepted formats are CSV, TSV, and XLSX, up to 50 MB and 50,000 rows. Warmbly reads the file on the server, so there is no client-side parsing to worry about.
2. **Map.** Warmbly shows your columns with a sample value from the first row, and suggests a mapping. You confirm or change which column maps to which field. There is a "First row is header" toggle if your file has no header row.
3. **Options.** Choose how to handle duplicates, optionally apply categories to the whole batch, and review what will happen.
4. **Result.** A summary of how many contacts were imported, updated, skipped, and failed. Any failed rows can be downloaded as an error CSV that you can fix and re-import.

You must map at least one column to **Email**. The wizard will not let you continue until you do. The standard fields you can map to are:

| Field | Notes |
| --- | --- |
| Email | Required. Used to dedupe. |
| First name | |
| Last name | |
| Company | |
| Phone | |
| Subscribed | A truthy value marks the contact as subscribed. |
| Categories | Existing category names applied to the contact. |
| Custom field | Anything else. You give it a name. See [Custom fields](#custom-fields). |

<Callout type="info" title="Duplicates are matched on lowercased email">
Warmbly deduplicates on the email address, lowercased, so `Dana@Acme.com` and `dana@acme.com` are treated as the same contact. You choose what happens when a row matches an existing contact:

- **Skip existing** leaves the existing contact untouched.
- **Update existing** merges the new values onto the existing contact.
- **Create duplicates** forces a new contact, falling back to update if a uniqueness rule blocks it.
</Callout>

Rows with a missing or invalid email are reported as errors and not imported. They show up in the result step with a line number and reason.

### From Google Sheets (lead sync)

A Google Sheet is not a one-time file upload. It is an on-demand **sync source**: you connect a sheet once, then pull fresh rows from it whenever you want by pressing "Sync now". Nothing syncs automatically until you ask for it.

The first time you set one up, the wizard runs through:

1. **Connect.** Authorize Warmbly to read your spreadsheets. Warmbly only reads the tab you choose and never writes back to the sheet. If you have already connected Google, this step is skipped.
2. **Sheet.** Paste the spreadsheet ID (the long ID in the sheet URL, between `/d/` and `/edit`), load its tabs, and pick the tab to import.
3. **Map.** The same column-mapping screen as the file import, run against the first rows of the chosen tab.
4. **Options.** Choose duplicate handling, give the source a label, optionally enroll synced contacts into a campaign, and optionally apply categories.
5. **Save.** Save the source on its own, or "Save and sync now" to pull rows immediately. A result summary appears just like a file import.

Saved sync sources live in your Sync sources list, where you can re-run, edit, or remove them. Each time you sync, Warmbly dedupes on lowercased email using the duplicate-handling option you picked, so re-syncing a sheet with new rows is safe.

## Custom fields

Standard fields cover identity (name, email, company, phone). For anything specific to your data, like an industry, a plan tier, or an account owner, use custom fields.

You can create custom fields two ways:

- **During import**, by mapping a column to "Use as custom field" and typing a field name.
- **On a contact**, by opening it and adding a name and value under the Custom fields section of the Details tab.

Custom fields are the main reason imports are worth getting right: they feed personalization. In a campaign email you reference a custom field the same way you reference a standard one.

<Callout type="info" title="Field names with spaces work in personalization">
A simple field name (letters, numbers, underscores) is referenced with a dot, like `{{.industry}}` or `{{.account_owner}}`, and can be used inside conditions and helper functions.

A field name with spaces or dashes, like `job title`, is also filled in. Write it exactly as named using the dot form:

```
Your role as {{.job title}} at {{.Company}} caught my eye.
```

Spaced or dashed field names work everywhere too, including inside `{{if}}` conditions and helper functions. Just write them with the dot, exactly as named (Warmbly resolves them for you).
</Callout>

See [Personalization & expressions](/guides/expressions) for the full templating language, including fallbacks, conditionals, functions, and spintax.

## Categories (tags)

Categories are colored labels you attach to contacts to group and filter them, for example `Warm lead`, `Conference 2026`, or `Enterprise`. They behave like tags.

You can apply categories from many places, all using the same picker:

- The bulk-edit panel when several contacts are selected.
- A single contact's Details tab.
- The new-contact dialog.
- The contact filters.
- The import and sync wizards, to tag a whole batch at once.

The picker supports type-ahead search. When you type a name that does not match an existing category, a **Create** option appears that adds the new category and selects it in one step. Each category has its own color, which is reused everywhere the category chip is shown.

Categories also drive campaign automation. A campaign sequence can run an **Add tag** or **Remove tag** action on a contact as they move through your flow, so a category can be applied automatically when, for example, someone replies positively.

## Deals and pipelines

Once a contact engages, you track the opportunity as a **deal** moving through a **pipeline**. A pipeline is a named sequence of **stages** a deal advances through, and you can define one per workflow.

### Pipelines and stages

On the Pipelines page you can:

- Create a pipeline with an initial set of stages. New pipelines start with `Open`, `Qualified`, and `Won` as a sensible default that you can edit.
- Add, rename, recolor, and delete stages. Each stage shows how many deals it currently holds.
- Rename or delete the whole pipeline. Deleting a pipeline removes its stages.

Stages are ordered, and that order is the left-to-right column order you see on the deal board.

### The Deals view: Table and Board

The Deals page has two views, toggled in the top bar:

- **Table** is the default. It shows every deal across all pipelines, served and paginated by the server, so it stays fast and accurate even with thousands of deals. Totals like open count and pipeline value are real sums computed over the whole result, not just the rows currently loaded.
- **Board** is a single-pipeline kanban. Pick a pipeline, and each stage becomes a column. Drag a deal card from one column to another to move it to that stage. Each column paginates its own deals with a "load more" control, and the column header count and open value come from the server summary, so they remain correct no matter how many cards are loaded.

A deal has a name, a stage, a value with a currency (defaulting to `USD`), an expected close date, and a status of `open`, `won`, or `lost`. New and existing deals open in the same dialog. The linked contact is shown read-only on the deal.

Deals can also be created and advanced automatically from a campaign. A sequence can run a **Create deal** action (into a pipeline and stage you pick) or a **Move deal stage** action as a contact progresses, so your CRM stays current without manual data entry.

## Suppression

A contact that should no longer receive mail is **suppressed**. The clearest case is an **unsubscribed** contact: unsubscribed contacts are skipped by every campaign automatically. You can toggle a contact's subscription on its Details tab, and the contacts list lets you filter to just Subscribed or Unsubscribed, with the Unsubscribed count labeled as suppressed.

Suppression also happens automatically: contacts that hard-bounce or mark mail as spam are suppressed and skipped by future sends. A campaign sequence can also run an **Unsubscribe** action to suppress a contact as part of your flow.

Suppression is the safe default response to a bad signal: rather than keep sending and collecting more bounces or complaints, Warmbly stops further traffic to that address. For the deliverability signals behind suppression (bounces, complaints, spam placement) and how they affect your sender reputation, see the [Deliverability](/guides/deliverability) guide.

## Where to go next

<Cards>
  <Card title="Personalization & expressions" href="/guides/expressions" />
  <Card title="Deliverability" href="/guides/deliverability" />
  <Card title="Automations" href="/guides/automations" />
</Cards>
