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.
- Add the connection from Data connections.
- Authorize and complete any required setup.
- Under Configuration → Data models, confirm the Conversation model for that connection.
- 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_idthrough the sender's email address matched tocontacts_v2, which requires contacts to be synced.
Using Claude (recommended)
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:
| Column | Property |
|---|---|
company_id | account_id |
engagement_id | conversation_id |
timestamp | timestamp |
timestamp | created_at |
sentiment | sentiment |
text_analysis | text_analysis |
label_analysis | label_analysis |
subject | email_subject |
key | key |
metadata | metadata |
encrypted_data | encrypted_data |
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_id → account_id, note_id → note_id, content → content, title → title, timestamp → timestamp + created_at.
Field reference
Model keys
| Property | Type | Description |
|---|---|---|
account_id | string | Account associated with the conversation. |
conversation_id | string | Stable ID for the thread in the source system. |
Default properties
| Property | Type | Description |
|---|---|---|
created_at | timestamp | When the conversation started or was opened. |
sentiment | float | Optional aggregate sentiment score. Pass <fs_analyze> + the text to have FunnelStory compute it automatically. |
Optional properties
| Property | Type | Description |
|---|---|---|
key | json | Structured key for deduplication: {"type": "email", "engagement_id": "...", "source": "hubspot"}. |
metadata | json | Extra display data shown in the timeline tooltip: {"title": "...", "link": "..."}. |
encrypted_data | json | Full text content stored encrypted: {"text": "..."}. |
text_analysis | json | Structured NLP output. Pass <fs_analyze> + text to trigger automatic analysis. |
label_analysis | json | Labels or topics extracted from content. Pass <fs_analyze> + text to trigger automatic labeling. |
email_subject | string | Subject line for email-sourced conversations. |
participants | json | List of participants for meeting or chat conversations. |
Related
- Note model — for HubSpot notes configured as the note type
- Support tickets
- Meetings
- Data models overview