# Track customers, companies and deals

URL: https://better-helpdesk.com/docs/app/crm/

Bring your organizations in as companies, set the lead and deal stages your team uses, add your own fields, and link each customer to your own tools.



Every person who writes in becomes a **contact**, and every organization a **company**. Agents work with both under Contacts and Companies in the agent UI, and with deals on the Deals board. This guide sets up what those screens hold. The words are defined in [Vocabulary](/docs/vocabulary).

## Bring in your organizations [#bring-in-your-organizations]

A company is matched to one of your organizations by its id. Return the user's organizations from `identify`, as in [Connect your sign-in](/docs/identity/sign-in):

```ts title="lib/identify.ts"
return {
  user: { id: user.id, email: user.email, name: user.name },
  orgs: session.memberships.map(membership => ({ id: membership.orgId, name: membership.orgName })),
  isAgent: user.role === 'support',
};
```

When a signed-in customer opens a conversation, the widget files it under one of their organizations: the one you pass as `orgId`, or else the first in `orgs`. With more than one organization the widget lets the customer choose. The company is created with the first conversation filed under it, and a contact who has no company yet is put in it.

```tsx
<HelpdeskWidget orgId={activeOrg.id} />
```

If `identify` cannot name an organization cheaply, leave `name` out and give the config [`orgNames`](/docs/reference/configuration#adapters-and-host-hooks), which is asked for the names it needs. To show agents facts about the organization from your own app, such as its plan, return them from [`resolveContext`](/docs/reference/configuration#adapters-and-host-hooks); they appear with its conversations and on its company page.

Agents can also make a company by hand, under Companies. In a conversation whose contact has no company, the agent UI offers the company that has the contact's email domain, or, when there is none and the address is not at a free mail provider, to create one from that domain.

## Set the lead stages [#set-the-lead-stages]

A contact and a company each have a lead stage, which an agent sets on its page. Both lists show it, and the contact list filters by it. Give the stages your team uses as `leadStages`:

```ts title="lib/helpdesk.ts"
export const helpdesk = buildHelpdesk({
  // ...
  leadStages: ['lead', 'trial', 'customer', 'churned'],
});
```

Without it, the stages are `lead`, `qualified`, `customer` and `churned`. A visitor who is not signed in and writes into an inbox keyed `sales` starts at the first stage in the list, or with no stage when the list is empty. Every other contact and company starts with no stage, until an agent sets one.

You can remove a stage that contacts or companies are still in. They keep it, the agent UI still shows it, and an agent can still save their other changes. Only a stage in the list can be set anew.

The agent UI translates the default stages. Name your own through `HelpdeskAdmin`'s `messages`, with the key `stage.` and the stage, as in [Change and translate the wording](/docs/customize/languages#replace-the-agent-uis-strings):

```ts
const messages = {
  en: { 'stage.trial': 'Trial' },
  de: { 'stage.trial': 'Testphase' },
};
```

## Set the deal stages [#set-the-deal-stages]

A deal belongs to a company or a contact, and has a title, a value in a currency, an expected close date and an owner. Give its stages, in order, as `dealStages`:

```ts title="lib/helpdesk.ts"
export const helpdesk = buildHelpdesk({
  // ...
  dealStages: ['new', 'demo', 'proposal', 'won', 'lost'],
});
```

Without it, the stages are `new`, `qualified`, `proposal`, `won` and `lost`. A new deal starts in the first stage. The Deals board shows one column per stage, in your order, with the total value of each; an agent drags a deal to move it, and a deal 14 days in one stage is flagged. A deal in a stage you remove from the list keeps it, in a column of its own at the end of the board, until an agent drags it into one of your stages. No stage has a meaning of its own to the helpdesk, `won` and `lost` included. Custom deal stages take their names from `messages` under `stage.` as well.

Agents create deals on the board, on a contact's or a company's page, and from a conversation of type `lead`, which fills in its contact and company. The agent who creates a deal owns it. A team that keeps its pipeline elsewhere sets `features: { deals: false }`: the board, the deals on contacts and companies and the button on a lead go, and the `agent/deals` routes answer 404.

## Add your own fields [#add-your-own-fields]

`customFields` adds fields to contacts, companies and deals:

```ts title="lib/helpdesk.ts"
export const helpdesk = buildHelpdesk({
  // ...
  customFields: {
    company: [
      { key: 'plan', label: { en: 'Plan', de: 'Abo' }, type: 'select', options: ['starter', 'team', 'enterprise'] },
      { key: 'seats', label: { en: 'Seats', de: 'Plätze' }, type: 'number' },
    ],
    contact: [{ key: 'role', label: { en: 'Role', de: 'Rolle' }, type: 'text' }],
  },
});
```

Each field needs a `key`, a label (English, plus any other language you want to show) and a `type`: `text`, `number`, `date`, or `select` with its `options`. The values are stored in the record's `custom` column, and a value of the wrong type, or for a key you have not defined, is refused unless the record already holds it. So when you remove a field, or an option of a `select`, the values records hold stay in place, and agents can still save those records. [CustomFieldDef](/docs/reference/configuration#customfielddef) describes each type's values.

The agent UI shows and edits the fields of contacts and companies on their pages, and those of a deal when an agent opens it on the Deals board.

## Record what happened [#record-what-happened]

A contact's and a company's page has a timeline: their conversations, and the notes, calls and meetings agents log there. To add what happens in your own app, such as an upgrade, call `helpdesk.track` from your server code, as in [Call the helpdesk from your own code](/docs/app/own-code#put-an-event-on-a-customers-timeline).

## Tag contacts and companies [#tag-contacts-and-companies]

Agents tag a contact or a company on its page, as a comma-separated list, up to 50 tags of up to 50 characters each. When an inbox has a [qualifying question](/docs/inbox/inboxes#inboxconfig), the answer a customer picks is added to their contact's tags. Conversation tags are separate; they are set on the conversation.

## Link to your own tools [#link-to-your-own-tools]

Your app probably has an admin page for each user and a billing page for each organization. The `links` option puts links to them next to the customer in the agent UI, so an agent reading a conversation does not have to look the customer up again.

### Return the links [#return-the-links]

`links` receives the contact and their company, and returns labeled URLs. The two carry your own ids: `userId` is the user's id in your app, and `orgId` the organization's.

```ts title="lib/helpdesk.ts"
export const helpdesk = buildHelpdesk({
  // ...
  links: (contact, company) => [
    ...(contact?.userId
      ? [
          {
            label: { en: 'Open in admin', de: 'In der Verwaltung öffnen' },
            url: `https://app.example.com/admin/users/${contact.userId}`,
          },
        ]
      : []),
    ...(company?.orgId
      ? [{ label: { en: 'Billing' }, url: `https://billing.example.com/orgs/${company.orgId}` }]
      : []),
  ],
});
```

Either argument can be `null`, and so can the ids. `userId` is set only for a customer your app signed in, through `identify` or an identity token; someone who only emailed or wrote in as a visitor has none. `orgId` is set only for a company that came from one of your organizations; a company an agent created, or one `better-helpdesk-import` created, has none. Return no link for what you cannot address. The contact and company also carry `id`, `name` and `email` or `domain`, if your tools look customers up by address instead. [Link types](/docs/reference/configuration#link-types) lists every field.

`links` may be `async`, for a link that needs a lookup first. It runs each time an agent opens a conversation, a contact or a company, inside that request, so keep it fast.

### Label them per language [#label-them-per-language]

`label` takes an entry per locale. The agent UI shows the agent's language, then English, then whichever label there is, and the URL itself when there is none.

### Where they appear [#where-they-appear]

The links show under the contact in a conversation, and on the contact's and the company's pages; the company page has no contact, so `links` receives `null` for it there. Each link opens in a new tab.

Only `http` and `https` URLs are shown; others are dropped. If `links` throws, the page shows no links and the error is logged as `[helpdesk] links failed`, so a broken lookup never keeps an agent from the conversation.
