Better Helpdesk
Your app and CRM

Track customers, companies and deals

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.

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:

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.

<HelpdeskWidget orgId={activeOrg.id} />

If identify cannot name an organization cheaply, leave name out and give the config orgNames, 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; 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

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:

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:

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

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:

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

customFields adds fields to contacts, companies and deals:

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

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.

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, the answer a customer picks is added to their contact's tags. Conversation tags are separate; they are set on the conversation.

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.

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.

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

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.

On this page