Skip to main content

Conversations model

The conversation model represents threaded discussions and communications — emails, chat threads, calls — that feed sentiment analysis, needle mover detection, and the account timeline. A conversation entry on the timeline shows the subject, sentiment, and a link back to the source.

Managed models from messaging connections

Many workspaces get a Conversation model automatically when you connect a supported messaging integration — for example Intercom, Slack, or Microsoft Teams.

  1. Add the connection from Data connections.
  2. Authorize and complete any required setup.
  3. Under Configuration → Data models, confirm the Conversation model for that connection.
  4. Refresh and validate on an account with recent conversations.

HubSpot: configuring email conversations

HubSpot email engagements are the most common source for conversation models. FunnelStory syncs them using the HubSpot v3 CRM API scoped to your deal companies, so only emails from accounts you manage are included.

Prerequisites

  • A connected HubSpot data connection.
  • The Accounts and Users models must be configured first. The email conversation query resolves account_id through the sender's email address matched to contacts_v2, which requires contacts to be synced.

The fastest way to configure HubSpot email conversations is to ask Claude via the MCP server:

"Configure a HubSpot email conversation model"

Claude reads the data model guide, previews the query against your data, and creates the model in the right order. See Configure HubSpot models with Claude for a full walkthrough.

Manual configuration

Add a Conversation model with the HubSpot connection and the following query:

WITH engs AS (
SELECT
engagements.id,
engagements.timestamp,
engagements.data->'engagement'->>'bodyPreview' AS text,
engagements.data->'metadata'->>'subject' AS subject,
engagements.data->'metadata'->'from'->>'email' AS from_email,
engagements.data->'engagement'->>'portalId' AS portal_id,
contacts_v2.properties->>'associatedcompanyid' AS company_id
FROM engagements
JOIN contacts_v2
ON LOWER(engagements.data->'metadata'->'from'->>'email') = LOWER(contacts_v2.email)
WHERE engagements.type IN ('INCOMING_EMAIL', 'EMAIL')
AND engagements.data->'engagement'->>'bodyPreview' IS NOT NULL
AND engagements.data->'engagement'->>'bodyPreview' != ''
AND engagements.data->'metadata'->'from'->>'email' IS NOT NULL
AND contacts_v2.properties->>'associatedcompanyid' IS NOT NULL
AND contacts_v2.properties->>'associatedcompanyid' != ''
GROUP BY engagements.id, engagements.timestamp, engagements.data,
contacts_v2.properties->>'associatedcompanyid'
)
SELECT
engs.id AS engagement_id,
engs.company_id,
engs.timestamp,
engs.subject,
json_object('type', 'email', 'engagement_id', engs.id, 'source', 'hubspot') AS key,
json_object('title', engs.subject) AS metadata,
json_object('text', engs.text) AS encrypted_data,
'<fs_analyze>' || engs.text AS sentiment,
'<fs_analyze>' || engs.text AS text_analysis,
'<fs_analyze>' || engs.text AS label_analysis
FROM engs
ORDER BY engs.timestamp DESC

Mappings:

ColumnProperty
company_idaccount_id
engagement_idconversation_id
timestamptimestamp
timestampcreated_at
sentimentsentiment
text_analysistext_analysis
label_analysislabel_analysis
subjectemail_subject
keykey
metadatametadata
encrypted_dataencrypted_data
How account resolution works

The query joins email sender addresses to contacts_v2.email and uses the contact's associatedcompanyid to find the account. This means the sender must be a known HubSpot contact associated with a deal company. Emails from unknown contacts are excluded.

HubSpot: configuring notes

HubSpot notes can also be configured as conversation models. Notes are associated to companies directly via the HubSpot associations API, so they don't require contact email matching.

Use model type note (not conversation) — notes appear in both the Notes page and the account timeline, and generate needle movers via the same AI analysis pipeline.

SELECT
e.id AS note_id,
ec.company_id AS account_id,
e.data->'engagement'->>'bodyPreview' AS content,
e.data->'metadata'->>'title' AS title,
e.timestamp
FROM engagements e
INNER JOIN engagement_companies ec ON e.id = ec.engagement_id
WHERE e.type = 'NOTE'
AND e.data->'engagement'->>'bodyPreview' IS NOT NULL
AND e.data->'engagement'->>'bodyPreview' != ''

Mappings: account_idaccount_id, note_idnote_id, contentcontent, titletitle, timestamptimestamp + created_at.

Field reference

Model keys

PropertyTypeDescription
account_idstringAccount associated with the conversation.
conversation_idstringStable ID for the thread in the source system.

Default properties

PropertyTypeDescription
created_attimestampWhen the conversation started or was opened.
sentimentfloatOptional aggregate sentiment score. Pass <fs_analyze> + the text to have FunnelStory compute it automatically.

Optional properties

PropertyTypeDescription
keyjsonStructured key for deduplication: {"type": "email", "engagement_id": "...", "source": "hubspot"}.
metadatajsonExtra display data shown in the timeline tooltip: {"title": "...", "link": "..."}.
encrypted_datajsonFull text content stored encrypted: {"text": "..."}.
text_analysisjsonStructured NLP output. Pass <fs_analyze> + text to trigger automatic analysis.
label_analysisjsonLabels or topics extracted from content. Pass <fs_analyze> + text to trigger automatic labeling.
email_subjectstringSubject line for email-sourced conversations.
participantsjsonList of participants for meeting or chat conversations.