Contacts & CRM
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, or from a segment's member list to pin every row into that segment. The wizard names the target it inherited in its header and on the Options step, and the result step confirms it. 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 |
|---|---|
| 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 |
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 campaigns, segments, 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.
Rows with a missing or invalid email are reported with a line number and reason rather than imported. Addresses are stored lowercased and stripped of any name around them, so a cell reading Dana Reyes <dana@acme.com> imports as dana@acme.com and both spellings of one address land on one contact. 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 SendSets cannot use, are reported once before anything is written, so a single typo can never fail every row.
The Options step also decides where the contacts land: Add to campaigns enrols everyone in the file as leads of the campaigns you pick (hidden when the wizard was started from a campaign's Leads tab, since that campaign is already the target), and Add to segments pins them into the segments you pick as manual includes, so they stay members whatever the segment's conditions say. Started from a segment, that segment is always applied and shown as a fixed row; the picker below it adds more.
From Google Sheets, a sheet is a reusable sync source rather than a one-time upload. Connect it once (SendSets 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, categories to apply and segments to pin into. Nothing syncs automatically: press Sync now, or save and sync immediately.
Segment targets apply on every run, so a sheet you keep adding rows to keeps feeding the same audience. Opening Sync sources from a segment's member list lists only the sources feeding it and pre-targets a new one to it. A segment deleted later is dropped from the run rather than stopping it.
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.
What the result tells you about the list
The result step also reports what the addresses themselves look like: how many were malformed, how many are on known throwaway domains, and how many are shared inboxes like info@. When a quarter or more of the file is malformed or throwaway, the result says so.
- Shared inboxes are counted but not held against the list. Mailing
info@is a choice, and plenty of legitimate B2B lists are mostly role addresses. - The import still happens. These are your records, so nothing is refused here. A list bad enough to matter is stopped when you try to launch a campaign with it, which is where the damage would actually occur. See the launch check.
- Lists under 20 rows are not judged, since a share of a handful of rows means nothing.
This is not address verification. It reads the addresses; verification asks the receiving server whether they exist, and runs separately in the background, within a minute of the import. A file that already carries verification results from another service (a status or ZeroBounce Status column, say) is recognised during mapping and offered as Verification status; those verdicts are kept and the background check skips them. See address verification.
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.
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.
See Personalization & expressions for the full templating language.
Filtering the list
A filter bar sits above the contact list. Category, Segment, Status and Campaign are always there; open one, tick values, and the list updates immediately with the matching count next to the bar. Add filter adds a custom-field condition (field, contains/is/starts with/ends with, value), a date-added or last-updated range, a number-of-campaigns range, the address verification verdict and, on a campaign's Leads tab, lead status and engagement. Each active filter is a pill you can reopen to change or remove with its cross; Clear drops them all, and Save as segment turns the current set into a segment. Free-text search and sort stay in the toolbar.
Free-text search matches first name, last name, email, company and phone. Every word you type has to match one of those, so Test Demo finds the contact whose first name is Test and last name is Demo, and Demo Acme finds everyone named Demo at Acme. Words can be in any order, and only the first six count.
Selecting rows
Tick a row's checkbox to select it, or the one in the table header to select every row loaded so far. Contact lists load in pages as you scroll, so the header checkbox on its own only ever covers what is on screen.
When more contacts match than are loaded, a bar appears under the header: Select all N matching. Clicking it hands the whole filtered set to the next action, however many pages that is, and the selection bar counts the full number rather than the loaded rows. Unticking a row afterwards takes just that contact out and the count follows. Clear selection in the bar, or the header checkbox, drops back to nothing.
The set is the one the list is showing: search, filters, the subscription facet, and the campaign or segment the list is scoped to all narrow it. Changing any of them clears the selection, because it would no longer mean what it did when you made it. A selection past 50,000 contacts is refused rather than half-applied; add a filter and work through it in parts.
Every bulk action reads the selection: Edit, Segment, Remove from segment, Remove from campaign, Research, Verify, Mark deliverable and Delete. Push to CRM is the exception: it calls the CRM once per contact while you wait, so it stays capped at 500 at a time.
Select all matching is in the From contacts picker too, on a campaign's Leads tab and a segment page, so a whole search can be added as leads or members in one step.
Editing one contact
Clicking a row opens the contact's panel. Its Details tab edits the fields the contact is made of: name, email address, company, phone, subscription, campaigns, categories and custom fields. Nothing is sent until Save, Discard puts the panel back to the stored values, and closing it with unsaved edits asks first.
The email address can be changed here, which is the right move when someone's address was mistyped on import or they moved to a new domain. It is stored lowercased and stripped of any display name, and it has to be free: an address another contact already holds is refused, because merging two people's campaign history is not something the edit could undo. A new address also clears the contact's verification verdict and everything the platform had observed about the old mailbox, so the background check starts the new address from scratch on its next pass, and the recipient provider SendSets matches senders against is worked out again from the new domain. Emails already sent went to the old address and stay in the timeline as they happened.
Editing many contacts at once
Tick rows in any contact list, a segment's members or a campaign's leads, and the selection bar's Edit opens a bulk panel. It can add or remove campaigns, add or remove categories, force a subscription state, and queue custom-field operations (add, edit, delete, rename) that run on every selected contact, including a Select all matching selection that reaches past the loaded pages.
Nothing is applied while you build it up. The header names the list the selection came from, and a Will apply strip above the buttons lists every queued change as a chip you can take back one at a time before pressing Apply. A field operation missing its key, or its value where one is needed, is marked and skipped rather than sent half-finished.
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 belong to the workspace, not to whoever made them: every teammate sees the same list and can file contacts under it. Contacts > Categories lists every category with a live contact count. From there you can create one, rename it, change its color, delete it (contacts are kept; the label is removed from them and from inbox threads), or click a row to open the contact list filtered to it.
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. Add to segment and Remove from segment do the same for segments.
To turn labels and activity into a reusable audience, build a segment: a saved set of conditions over contacts (categories, fields, campaign activity, engagement) that you can browse and add to a campaign in one step.
Where a contact came from
Every contact records its first-touch source: how it entered the workspace, with a detail you would recognise, and when it was first seen. It is shown on the contact's Overview tab and as the first event of its activity timeline, and it never changes afterwards, however the contact is edited or re-imported.
| Source | Set when | Detail |
|---|---|---|
| Manual | Added with Add contact on the Contacts page | |
| Campaign | Added with Add lead on a campaign's Leads tab | The campaign |
| Import | Created by the file import wizard | The file name |
| Sheet sync | Created by a Google Sheets sync source | The sheet |
| API | Created through the API with an API key | The key's name |
| Form | Submitted a hosted form | The form |
| Automation | Written by an automation's Create or update contact action | The automation |
| AI assistant | Created by the assistant on your behalf | |
| Unknown | Created before SendSets recorded sources |
Only new contacts get a source. An import or API call that matches an existing contact updates its fields and leaves the original source in place, so an address that arrived by hand and later turned up in a file still reads as manual.
A new contact is also an event. contact.created goes to your webhooks and can start an automation with the contact's fields and source, except for bulk arrivals, which stay silent: file imports, sheet syncs, and a single API request adding more than 100 contacts.
Activity timeline
The Activity tab of a contact is one workspace-wide feed, newest first, of everything SendSets knows about them. Every member with permission to view contacts sees the same timeline, regardless of who created the contact or its campaigns. It includes every campaign email sent, opened, clicked, replied to or bounced (with the campaign, step, subject and sending mailbox), replies with their classified intent, deliverability and suppression events, notes, meetings, and the contact's lifecycle: when it was created and how, and each time it joined or left a campaign or a category.
Opens appear once per event, not once per email: a second open from another device is its own row. Opens and clicks show the mail client or browser they came from and the city and country when known (see what is recorded per open and click); expanded, the operating system, device and full location. A click names the link: the row reads Clicked Pricing and, expanded, shows the link's text, its full URL, the UTM source, medium, campaign and content it carried, and the browser. Every link in an email is tracked on its own, so two links clicked are two rows. Opens and clicks that came from a machine rather than the person (a mail privacy proxy, a security gateway that follows every link at delivery) carry an auto badge, and the expanded row says which rule caught them; see Link tracking and UTM parameters.
Filter chips narrow the feed (Emails, Replies, Deliv., Notes, Meetings, Campaigns, Lifecycle), the search box matches subjects, campaigns, steps, mailboxes, categories, reasons and, for clicks, the link's text, URL and UTM values, and the date picker bounds it. Each row stays to one line until you click it; expanded, it shows every detail the event carries. The feed updates live as teammates and the schedulers write to it.
At the top of the tab sits the campaign panel: for each campaign the contact is in, its flow with this contact's progress, the lead status, and what the scheduler will do next. See Campaigns for how the next action is worked out and what its states mean.
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.
Tasks
CRM > Tasks is the follow-up list for the whole workspace: everything anyone has to do about a contact or a deal, in one place. A task carries a title, a description, a type (Call, Email, Meeting by default, and any type you add), a due date, a priority of low, medium, high or urgent, a status of pending, in progress, completed or cancelled, and an assignee who can be a person, a team, or nobody.
Two views share one list. Table is the flat list with a column per field; By due date groups it into Overdue, Today, Tomorrow, This week, Later and No due date. Both filter, sort and page on the server, so the Overdue, High priority, Pending, Active and Completed totals across the top count the whole matching set rather than the rows loaded so far. The status tabs, the search box, the assignee, team and type facets and the filter popover all narrow the same set.
Clicking a row opens it for editing. Each row carries two controls before its title: the checkbox at the far left selects it for a bulk action, and the square next to the title marks it done on its own.
Selecting many tasks
Tick a row to select it, the checkbox in the table header to select every row loaded so far, or a group's header in the by-due-date view to select that group. When more tasks match than are loaded, a bar appears offering Select all N matching, which hands the whole filtered set to the next action however many pages that is. Unticking a row afterwards takes just that task out and the count follows.
The selection means the filter it was made under, so changing any filter clears it. A selection past 50,000 tasks is refused rather than half applied; add a filter and work through it in parts.
With rows selected, a bar appears at the bottom: Mark done, Status (any of the four), Priority (any of the four), and Delete, which confirms first and names the count. This is how you clear a backlog of tasks nobody needs without opening each one. To clear the follow-ups a workspace collected before it narrowed the setting below, search for out_of_office, tick the header checkbox, take Select all N matching and delete them in one step.
Follow-up tasks from replies
SendSets can open a task by itself when a reply lands, so a prospect who answers ends up on this list rather than only in the inbox. The task is assigned to the owner of the mailbox that received the reply, due in 24 hours, and titled with the intent and the sender: Follow up: positive reply from dana@acme.com.
Which replies get one is up to you, in Settings > Sending > Reply follow-ups. Every reply is classified first, and the classifier can tell a person from a machine: an out-of-office notice, a helpdesk autoresponder, a bounce and a delivery report are recognized from the message headers, not only from the words in the body, so an auto-responder that never mentions a vacation is still caught.
| Intent | Opens a task by default |
|---|---|
| Positive: interested, wants a call | Yes |
| Question: asked something | Yes |
| Neutral: a human reply we could not bucket | Yes |
| Negative: not interested | Yes |
| Automated: out of office, autoresponders, bounces | No |
Automated replies are off deliberately. A vacation notice is not follow-up work, and a week of sending makes enough of them to bury the real replies. Tick Automated if you do want one per machine reply, or untick the human ones you do not need. Unticking everything is the same as turning the switch off.
The classification does more than decide the task: an automated reply never counts as a reply for stop on reply or for a replied branch in a sequence, and its notification goes to the Out-of-office detected preference rather than New reply, so muting one does not mute the other.
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 when a recipient unsubscribes or replies asking to stop, on a hard bounce or spam complaint, and when a sequence runs an Unsubscribe action. The Suppression list tab holds every entry, takes addresses and whole domains by hand, and lets you lift an entry; a suppressed contact's Overview tab shows why and offers the same.
This is the safe default response to a bad signal: stop sending rather than keep collecting bounces and complaints. See unsubscribe and suppression for the full picture and Deliverability for the signals behind it.