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

Contacts are the people you reach out to; the CRM tracks what happens after they reply.

## Importing

Two routes share the same column-mapping screen: a file upload and an on-demand Google Sheets sync.

**From a file** (CSV, TSV, or XLSX, up to 50 MB and 50,000 rows), the wizard runs **Upload**, **Map**, **Options**, **Result**. Parsing happens server-side. Start it from Contacts, or from a campaign's Leads tab to attach the imports to that campaign automatically. The result step reports imported, updated, skipped, and failed counts, and failed rows download as an error CSV you can fix and re-import.

You must map at least one column to **Email**.

| Field | Notes |
| --- | --- |
| Email | Required, and used to dedupe |
| First name, Last name, Company, Phone | Standard identity fields |
| Subscribed | `yes`, `true`, `1`, `subscribed` and their opposites (`no`, `false`, `0`, `unsubscribed`). Applies to new contacts and to existing ones you update; a value the importer cannot read fails only that row |
| Categories | Category names, separated by commas or semicolons. Names you do not have yet are created, up to 100 new names per import |
| Custom field | Anything else; you name it |

<Callout type="info" title="Duplicates match on lowercased email">
`Dana@Acme.com` and `dana@acme.com` are the same contact. Choose **Skip existing** (leave untouched), **Update existing** (merge new values in), or **Create duplicates** (force a new one, falling back to update if a uniqueness rule blocks it).

Skipping still adds the contact to the campaign and categories the import targets: it leaves their fields alone, it does not leave them out of the list. If the same address appears twice in one file it becomes one contact, and the extra rows count as skipped.
</Callout>

Rows with a missing or invalid email are reported with a line number and reason rather than imported. A blank cell never erases a value you already have, so re-importing a partial export enriches contacts instead of wiping them.

Problems with the mapping itself, an unnamed custom field or a name Warmbly cannot use, are reported once before anything is written, so a single typo can never fail every row.

**From Google Sheets**, a sheet is a reusable **sync source** rather than a one-time upload. Connect it once (Warmbly reads only the tab you choose and never writes back), paste the spreadsheet ID from the URL between `/d/` and `/edit`, pick the tab, map columns, then set duplicate handling, a label, and optionally a campaign to enroll into and categories to apply. **Nothing syncs automatically**: press **Sync now**, or save and sync immediately.

Saved sources live in your Sync sources list to re-run, edit, or remove. Each sync dedupes on lowercased email using your chosen duplicate handling, so re-syncing a sheet with new rows is safe.

## Custom fields

For anything beyond identity: industry, plan tier, account owner. Create them by mapping a column to "Use as custom field" during import, or on a contact's Details tab.

They feed personalization, which is the main reason to get imports right.

Field names may use letters, numbers, underscores, spaces, and dashes: `industry`, `account_owner`, `Company Mobile`, `first-name`. Other characters (`/`, `#`, `(`, `)`) are rejected because there would be no way to merge that field into an email. When you map a column to a custom field the wizard fills the name in from the column header and flags it inline if it cannot be used.

<Callout type="info" title="Field names with spaces work too">
Simple names (letters, numbers, underscores) use a dot: `{{.industry}}`, `{{.account_owner}}`. Names with spaces or dashes work the same way, written exactly as named:

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

Both forms work everywhere, including inside `{{if}}` conditions and helper functions.
</Callout>

See [Personalization & expressions](/guides/expressions/) for the full templating language.

## Categories

Colored labels that group and filter contacts (`Warm lead`, `Conference 2026`, `Enterprise`), behaving like tags. The same picker appears in bulk edit, a contact's Details tab, the new-contact dialog, the filters, and the import and sync wizards.

It supports type-ahead search, and typing an unmatched name offers **Create** to add and select it in one step. Each category keeps its color everywhere its chip appears.

Categories also drive automation: a sequence can run **Add tag** or **Remove tag** as a contact moves through a flow, so a label can be applied automatically on a positive reply.

## Deals and pipelines

A **pipeline** is a named, ordered sequence of **stages** a deal advances through. On the Pipelines page you create pipelines (new ones start with `Open`, `Qualified`, `Won`), then add, rename, recolor, and delete stages, each showing its deal count. Stage order is the left-to-right column order on the board.

| View | Behavior |
|------|----------|
| **Table** (default) | Every deal across all pipelines, server-paginated. Totals like open count and pipeline value are real sums over the whole result, not just loaded rows |
| **Board** | Single-pipeline kanban, one column per stage, drag cards to move stages. Each column paginates itself, and header counts come from the server summary |

A deal has a name, stage, value and currency (default `USD`), expected close date, and a status of `open`, `won`, or `lost`. The linked contact is shown read-only.

Campaigns can create and advance deals automatically with **Create deal** and **Move deal stage** actions, so the CRM stays current without manual entry.

## Suppression

A suppressed contact receives no further mail. Unsubscribed contacts are skipped by every campaign automatically; toggle subscription on the Details tab, and filter the list by Subscribed or Unsubscribed.

Suppression also happens automatically on a hard bounce or spam complaint, and a sequence can run an **Unsubscribe** action.

This is the safe default response to a bad signal: stop sending rather than keep collecting bounces and complaints. See [Deliverability](/guides/deliverability/) for the signals behind it.

## 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>
