Skip to content
This repository was archived by the owner on Jun 26, 2026. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion content/docs/agents/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"...custom-code-agent",

"---Observability---",
"conversations"
"conversations",

"---Pricing---",
"pricing"
]
}
119 changes: 119 additions & 0 deletions content/docs/agents/pricing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
title: "Pricing"
pageTitle: 'Pricing'
description: 'Plans, limits, and billing for Novu Connect (Agent Communication Infrastructure).'
icon: CreditCard
---

import { ConnectPricingCalculator } from '@/components/agents/pricing-calculator';

Check notice on line 8 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L8

[Google.Semicolons] Use semicolons judiciously.
Raw output
{"message": "[Google.Semicolons] Use semicolons judiciously.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 8, "column": 82}}}, "severity": "INFO"}

Novu Connect bills on your plan, the number of **active conversations** per billing cycle, and how many agents and channel connections you keep running. Agents are included in your current Novu subscription. You do not need a separate Connect subscription. Workflow run overage and agent conversation overage are billed separately.

Check notice on line 10 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L10

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 10, "column": 161}}}, "severity": "INFO"}

Check warning on line 10 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L10

[write-good.Passive] 'are included' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are included' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 10, "column": 161}}}, "severity": "WARNING"}

Check notice on line 10 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L10

[Google.Passive] In general, use active voice instead of passive voice ('are included').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are included').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 10, "column": 161}}}, "severity": "INFO"}

Check notice on line 10 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L10

[Google.Contractions] Use 'don't' instead of 'do not'.
Raw output
{"message": "[Google.Contractions] Use 'don't' instead of 'do not'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 10, "column": 213}}}, "severity": "INFO"}

Check notice on line 10 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L10

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 10, "column": 310}}}, "severity": "INFO"}

Check warning on line 10 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L10

[write-good.Passive] 'are billed' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are billed' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 10, "column": 310}}}, "severity": "WARNING"}

Check notice on line 10 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L10

[Google.Passive] In general, use active voice instead of passive voice ('are billed').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are billed').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 10, "column": 310}}}, "severity": "INFO"}

## Plans

Connect limits follow your Novu plan tier. The table below compares price, conversation volume, and the features that change between tiers.

| | Free | Pro | Team | Enterprise |
| --- | --- | --- | --- | --- |
| **Price** | $0 | $30 / mo | $250 / mo | Custom |
| **Active conversations** | 100 | 1,000 | 5,000 | Custom |
| **Extra conversation** | Not available | $0.02 | $0.015 | Custom |
| **Agents** | 2 | 5 | 10 | Unlimited |
| **Channels** | 2 | 5 | All available | All available |
| **Branding** | Novu branding shown | Removable | Removable | Removable |
| **Custom email domain** | No | No | Yes | Yes |
| **History** | 24 hours | 7 days | 90 days | Custom |

Free stops counting new conversations when you hit 100 until your billing cycle resets or you upgrade. Pro and Team bill overage at the rates above. Enterprise includes HIPAA BAA, SSO and SCIM, audit logs, and dedicated support. Contact [sales@novu.co](mailto:sales@novu.co) for Enterprise pricing.

Check warning on line 27 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L27

[Google.OxfordComma] Use the Oxford comma in 'Enterprise includes HIPAA BAA, SSO and'.
Raw output
{"message": "[Google.OxfordComma] Use the Oxford comma in 'Enterprise includes HIPAA BAA, SSO and'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 27, "column": 150}}}, "severity": "WARNING"}

Check notice on line 27 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L27

[Google.Acronyms] Spell out 'HIPAA', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'HIPAA', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 27, "column": 170}}}, "severity": "INFO"}

Check notice on line 27 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L27

[Google.Acronyms] Spell out 'BAA', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'BAA', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 27, "column": 176}}}, "severity": "INFO"}

Check notice on line 27 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L27

[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 27, "column": 181}}}, "severity": "INFO"}

Check notice on line 27 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L27

[Google.Acronyms] Spell out 'SCIM', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'SCIM', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 27, "column": 189}}}, "severity": "INFO"}

## Estimate your cost

Pick a plan and set your expected conversation volume to see how base price and overage add up for one billing cycle.

<ConnectPricingCalculator />

## Active conversations

This is the main unit Connect uses for billing. An **active conversation** is a thread where an agent is involved. You are billed once per billing cycle for that thread, no matter how many messages are exchanged while it stays active.

Check notice on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 6}}}, "severity": "INFO"}

Check notice on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 76}}}, "severity": "INFO"}

Check warning on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[write-good.Passive] 'is involved' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is involved' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 103}}}, "severity": "WARNING"}

Check notice on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[Google.Passive] In general, use active voice instead of passive voice ('is involved').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is involved').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 103}}}, "severity": "INFO"}

Check warning on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[write-good.Passive] 'are billed' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are billed' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 120}}}, "severity": "WARNING"}

Check notice on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[Google.Passive] In general, use active voice instead of passive voice ('are billed').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are billed').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 120}}}, "severity": "INFO"}

Check notice on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 120}}}, "severity": "INFO"}

Check notice on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 199}}}, "severity": "INFO"}

Check notice on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[Google.Passive] In general, use active voice instead of passive voice ('are exchanged').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are exchanged').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 199}}}, "severity": "INFO"}

Check warning on line 37 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L37

[write-good.Passive] 'are exchanged' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are exchanged' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 37, "column": 199}}}, "severity": "WARNING"}

In Connect, every conversation is **active**, **resolved**, or **reopened**. Resolving a conversation ends its active period for billing. If the user messages again and Connect reopens it, that counts as a new active conversation. For lifecycle details, refer to [Agent Conversations](/agents/conversations).

Check notice on line 39 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L39

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 39, "column": 32}}}, "severity": "INFO"}

### What counts

1. A user starts a thread, or a resolved conversation is reopened.

Check warning on line 43 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L43

[write-good.Passive] 'is reopened' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is reopened' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 43, "column": 55}}}, "severity": "WARNING"}

Check notice on line 43 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L43

[Google.Passive] In general, use active voice instead of passive voice ('is reopened').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is reopened').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 43, "column": 55}}}, "severity": "INFO"}

Check notice on line 43 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L43

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 43, "column": 55}}}, "severity": "INFO"}
2. That counts as one active conversation for the current billing cycle.
3. More messages in the same active thread do not add to the count.

Check notice on line 45 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L45

[Google.Contractions] Use 'don't' instead of 'do not'.
Raw output
{"message": "[Google.Contractions] Use 'don't' instead of 'do not'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 45, "column": 44}}}, "severity": "INFO"}
4. When the conversation is resolved, it stops being active.

Check warning on line 46 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L46

[write-good.Passive] 'is resolved' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is resolved' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 46, "column": 26}}}, "severity": "WARNING"}

Check notice on line 46 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L46

[Google.Passive] In general, use active voice instead of passive voice ('is resolved').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is resolved').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 46, "column": 26}}}, "severity": "INFO"}

Check notice on line 46 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L46

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 46, "column": 26}}}, "severity": "INFO"}

Check notice on line 46 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L46

[write-good.E-Prime] Try to avoid using 'being'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'being'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 46, "column": 48}}}, "severity": "INFO"}
5. A later reopen starts a new active conversation.

### Billing cycle comes first

Your billing cycle is the main window for counting conversations. Retention limits and platform session rules only matter when they end sooner than your billing cycle.

Check notice on line 51 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L51

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 51, "column": 20}}}, "severity": "INFO"}

If a conversation starts at 11:00 AM today and your plan renews tomorrow at 7:00 AM, it counts only until 7:00 AM. It does not carry into the next cycle just because the thread is still open on the channel.

Check notice on line 53 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L53

[write-good.E-Prime] Try to avoid using 'AM'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'AM'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 53, "column": 35}}}, "severity": "INFO"}

Check notice on line 53 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L53

[write-good.E-Prime] Try to avoid using 'AM'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'AM'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 53, "column": 82}}}, "severity": "INFO"}

Check notice on line 53 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L53

[write-good.E-Prime] Try to avoid using 'AM'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'AM'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 53, "column": 112}}}, "severity": "INFO"}

Check notice on line 53 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L53

[Google.Contractions] Use 'doesn't' instead of 'does not'.
Raw output
{"message": "[Google.Contractions] Use 'doesn't' instead of 'does not'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 53, "column": 119}}}, "severity": "INFO"}

Check notice on line 53 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L53

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 53, "column": 178}}}, "severity": "INFO"}

Connect applies whichever limit ends first: billing cycle reset, plan history retention, or the platform's own session rules.

### By platform

Platforms handle threads differently. Connect maps each one to the same active, resolved, and reopened model.

| Platform | One active conversation | New conversation starts when |
| --- | --- | --- |
| **Slack** | One DM or channel thread with the agent, even with many messages or participants | The conversation is resolved and reopened, or a new thread starts |

Check notice on line 63 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L63

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 63, "column": 115}}}, "severity": "INFO"}

Check warning on line 63 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L63

[write-good.Passive] 'is resolved' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is resolved' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 63, "column": 115}}}, "severity": "WARNING"}

Check notice on line 63 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L63

[Google.Passive] In general, use active voice instead of passive voice ('is resolved').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is resolved').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 63, "column": 115}}}, "severity": "INFO"}
| **Microsoft Teams** | One chat or channel thread with the agent | Same as Slack |
| **Email** | One thread with the same subject and participants | The conversation is resolved and a later reply reopens it |

Check warning on line 65 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L65

[write-good.Passive] 'is resolved' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is resolved' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 65, "column": 84}}}, "severity": "WARNING"}

Check notice on line 65 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L65

[Google.Passive] In general, use active voice instead of passive voice ('is resolved').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is resolved').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 65, "column": 84}}}, "severity": "INFO"}

Check notice on line 65 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L65

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 65, "column": 84}}}, "severity": "INFO"}
| **WhatsApp** | One user chat within the 24-hour messaging window | The session expires and the user sends a new message |
| **Telegram** | One chat with the bot | A new session starts after the previous one was resolved |

Check warning on line 67 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L67

[write-good.Passive] 'was resolved' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'was resolved' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 67, "column": 86}}}, "severity": "WARNING"}

Check notice on line 67 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L67

[Google.Passive] In general, use active voice instead of passive voice ('was resolved').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('was resolved').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 67, "column": 86}}}, "severity": "INFO"}

Check notice on line 67 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L67

[write-good.E-Prime] Try to avoid using 'was'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'was'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 67, "column": 86}}}, "severity": "INFO"}

| Scenario | Billed as |
| --- | --- |
| 80 messages in one Slack DM all month | 1 active conversation |
| Agent and six teammates in one Slack channel all week | 1 active conversation |
| Email resolved on the 10th, reopened on the 20th (same cycle) | 2 active conversations |

Check failure on line 73 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L73

[Google.Ordinal] Spell out all ordinal numbers ('10th') in text.
Raw output
{"message": "[Google.Ordinal] Spell out all ordinal numbers ('10th') in text.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 73, "column": 25}}}, "severity": "ERROR"}

Check failure on line 73 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L73

[Google.Ordinal] Spell out all ordinal numbers ('20th') in text.
Raw output
{"message": "[Google.Ordinal] Spell out all ordinal numbers ('20th') in text.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 73, "column": 47}}}, "severity": "ERROR"}

Check notice on line 73 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L73

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 73, "column": 52}}}, "severity": "INFO"}
| WhatsApp 24-hour window expires, user replies the next day | 2 active conversations |
| Activity in January, none in February | 1 in January, 0 in February |
| One-way outbound email with no reply path | 1 active conversation |
| Thread starts at 11:00 AM today, plan renews at 7:00 AM tomorrow | 1 in the current cycle, counted until 7:00 AM |

Check notice on line 77 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L77

[write-good.E-Prime] Try to avoid using 'AM'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'AM'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 77, "column": 26}}}, "severity": "INFO"}

Check notice on line 77 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L77

[write-good.E-Prime] Try to avoid using 'AM'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'AM'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 77, "column": 56}}}, "severity": "INFO"}

Check notice on line 77 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L77

[write-good.E-Prime] Try to avoid using 'AM'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'AM'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 77, "column": 113}}}, "severity": "INFO"}

## Agents and channels

These limits control how much Connect infrastructure you can run in parallel. They are capacity caps, not metered add-ons.

Check notice on line 81 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L81

[Google.Contractions] Use 'they're' instead of 'They are'.
Raw output
{"message": "[Google.Contractions] Use 'they're' instead of 'They are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 81, "column": 79}}}, "severity": "INFO"}

Check notice on line 81 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L81

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 81, "column": 84}}}, "severity": "INFO"}

**Agents** are distinct personas or bots in your workspace. One agent on four channels is still one agent. Paused agents do not count.

Check notice on line 83 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L83

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 83, "column": 12}}}, "severity": "INFO"}

Check notice on line 83 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L83

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 83, "column": 88}}}, "severity": "INFO"}

Check notice on line 83 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L83

[Google.Contractions] Use 'don't' instead of 'do not'.
Raw output
{"message": "[Google.Contractions] Use 'don't' instead of 'do not'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 83, "column": 122}}}, "severity": "INFO"}

**Channels** are external connections: one Slack workspace, one WhatsApp number, one Telegram bot, or one email domain. Multiple Slack channels in the same workspace count as one connection.

Check notice on line 85 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L85

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 85, "column": 14}}}, "severity": "INFO"}

Check warning on line 85 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L85

[write-good.TooWordy] 'Multiple' is too wordy.
Raw output
{"message": "[write-good.TooWordy] 'Multiple' is too wordy.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 85, "column": 121}}}, "severity": "WARNING"}

At your limit, upgrade or pause an existing agent or channel. Connect does not bill per extra agent or channel.

Check warning on line 87 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L87

[Google.OxfordComma] Use the Oxford comma in 'At your limit, upgrade or'.
Raw output
{"message": "[Google.OxfordComma] Use the Oxford comma in 'At your limit, upgrade or'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 87, "column": 1}}}, "severity": "WARNING"}

Check notice on line 87 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L87

[Google.Contractions] Use 'doesn't' instead of 'does not'.
Raw output
{"message": "[Google.Contractions] Use 'doesn't' instead of 'does not'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 87, "column": 71}}}, "severity": "INFO"}

## Included on every plan

Every tier ships with the same channels, agent providers, and core Connect capabilities. Plans differ in volume and team features, not in what you can build.

- **Channels:** Email, Slack, Microsoft Teams, WhatsApp, Telegram

Check warning on line 93 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L93

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 93, "column": 1}}}, "severity": "WARNING"}
- **Agent providers:** Managed Claude Code, AWS Bedrock, Google Vertex AI, Azure AI Foundry, Claude Platform on AWS, Mistral, LangChain and LangGraph, custom code

Check warning on line 94 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L94

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 94, "column": 1}}}, "severity": "WARNING"}

Check notice on line 94 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L94

[Google.Acronyms] Spell out 'AWS', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'AWS', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 94, "column": 45}}}, "severity": "INFO"}

Check notice on line 94 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L94

[Google.Acronyms] Spell out 'AWS', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'AWS', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 94, "column": 113}}}, "severity": "INFO"}
- **Capabilities:** Identity resolution, tools and skills, MCP OAuth, credentials vault, shared context across channels, streaming, reactions, replies, actions, forms, cards

Check warning on line 95 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L95

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 95, "column": 1}}}, "severity": "WARNING"}

Check notice on line 95 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L95

[Google.Acronyms] Spell out 'MCP', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'MCP', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 95, "column": 60}}}, "severity": "INFO"}

## FAQ

Check warning on line 97 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L97

[Google.Headings] 'FAQ' should use sentence-style capitalization.
Raw output
{"message": "[Google.Headings] 'FAQ' should use sentence-style capitalization.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 97, "column": 4}}}, "severity": "WARNING"}

Answers to common questions about conversation limits, plan changes, and compliance.

### What happens when I exceed my conversation limit?

Check warning on line 101 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L101

[Google.FirstPerson] Avoid first-person pronouns such as ' I '.
Raw output
{"message": "[Google.FirstPerson] Avoid first-person pronouns such as ' I '.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 101, "column": 22}}}, "severity": "WARNING"}

Check warning on line 101 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L101

[Google.FirstPerson] Avoid first-person pronouns such as 'my'.
Raw output
{"message": "[Google.FirstPerson] Avoid first-person pronouns such as 'my'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 101, "column": 32}}}, "severity": "WARNING"}

Pro and Team bill overage at $0.02 and $0.015 per extra conversation. Free is capped until your cycle resets or you upgrade. Enterprise uses committed volume.

Check warning on line 103 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L103

[write-good.Passive] 'is capped' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is capped' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 103, "column": 76}}}, "severity": "WARNING"}

Check notice on line 103 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L103

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 103, "column": 76}}}, "severity": "INFO"}

Check notice on line 103 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L103

[Google.Passive] In general, use active voice instead of passive voice ('is capped').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is capped').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 103, "column": 76}}}, "severity": "INFO"}

### What happens when I hit my agent or channel limit?

Check warning on line 105 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L105

[Google.FirstPerson] Avoid first-person pronouns such as ' I '.
Raw output
{"message": "[Google.FirstPerson] Avoid first-person pronouns such as ' I '.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 105, "column": 22}}}, "severity": "WARNING"}

Check warning on line 105 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L105

[Google.FirstPerson] Avoid first-person pronouns such as 'my'.
Raw output
{"message": "[Google.FirstPerson] Avoid first-person pronouns such as 'my'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 105, "column": 29}}}, "severity": "WARNING"}

You upgrade or disable an existing agent or channel. Connect does not meter agents or channels separately.

Check notice on line 107 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L107

[Google.Contractions] Use 'doesn't' instead of 'does not'.
Raw output
{"message": "[Google.Contractions] Use 'doesn't' instead of 'does not'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 107, "column": 62}}}, "severity": "INFO"}

### Are channels or agent providers limited to higher plans?

Check notice on line 109 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L109

[write-good.E-Prime] Try to avoid using 'Are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'Are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 109, "column": 5}}}, "severity": "INFO"}

No. Every channel and provider is on every plan. Plans differ in how many agents and connections you can run, plus team and security features.

Check notice on line 111 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L111

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 111, "column": 32}}}, "severity": "INFO"}

### Can I change plans without losing data?

Check warning on line 113 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L113

[Google.FirstPerson] Avoid first-person pronouns such as ' I '.
Raw output
{"message": "[Google.FirstPerson] Avoid first-person pronouns such as ' I '.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 113, "column": 8}}}, "severity": "WARNING"}

Yes. Agents, conversations, and connections stay in place. On downgrade, items above the new limit are paused, not deleted.

Check notice on line 115 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L115

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 115, "column": 100}}}, "severity": "INFO"}

Check notice on line 115 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L115

[Google.Passive] In general, use active voice instead of passive voice ('are paused').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are paused').", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 115, "column": 100}}}, "severity": "INFO"}

Check warning on line 115 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L115

[write-good.Passive] 'are paused' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are paused' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 115, "column": 100}}}, "severity": "WARNING"}

### Is Connect HIPAA compliant?

Check warning on line 117 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L117

[Google.Headings] 'Is Connect HIPAA compliant?' should use sentence-style capitalization.
Raw output
{"message": "[Google.Headings] 'Is Connect HIPAA compliant?' should use sentence-style capitalization.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 117, "column": 5}}}, "severity": "WARNING"}

Check notice on line 117 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L117

[write-good.E-Prime] Try to avoid using 'Is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'Is'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 117, "column": 5}}}, "severity": "INFO"}

Check notice on line 117 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L117

[Google.Acronyms] Spell out 'HIPAA', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'HIPAA', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 117, "column": 16}}}, "severity": "INFO"}

HIPAA BAAs are on Enterprise. Contact [sales@novu.co](mailto:sales@novu.co) if you need to route protected health information through Connect.

Check notice on line 119 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L119

[Google.Acronyms] Spell out 'HIPAA', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'HIPAA', if it's unfamiliar to the audience.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 119, "column": 1}}}, "severity": "INFO"}

Check notice on line 119 in content/docs/agents/pricing.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/agents/pricing.mdx#L119

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/agents/pricing.mdx", "range": {"start": {"line": 119, "column": 12}}}, "severity": "INFO"}
243 changes: 243 additions & 0 deletions src/components/agents/pricing-calculator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
'use client';

import { useMemo, useState } from 'react';
import { cn } from '@/lib/cn';

type PlanId = 'free' | 'pro' | 'team';

type Plan = {
id: PlanId;
name: string;
monthlyPrice: number;
includedConversations: number;
overageRate: number | null;
agents: number | 'Unlimited';
channels: string;
};

const PLANS: Plan[] = [
{
id: 'free',
name: 'Free',
monthlyPrice: 0,
includedConversations: 100,
overageRate: null,
agents: 2,
channels: '2',
},
{
id: 'pro',
name: 'Pro',
monthlyPrice: 30,
includedConversations: 1000,
overageRate: 0.02,
agents: 5,
channels: '5',
},
{
id: 'team',
name: 'Team',
monthlyPrice: 250,
includedConversations: 5000,
overageRate: 0.015,
agents: 10,
channels: 'All available',
},
];

const MAX_CONVERSATIONS = 20000;
const FREE_TO_PRO_THRESHOLD = 300;
const PRO_TEAM_TO_FREE_THRESHOLD = 100;

function formatCurrency(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: value % 1 === 0 ? 0 : 2,
maximumFractionDigits: 2,
}).format(value);
Comment on lines +52 to +58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 formatCurrency truncates Team overage rate to wrong value

maximumFractionDigits: 2 causes formatCurrency(0.015) to render as $0.01 instead of $0.015. In JavaScript, 0.015 is stored as 0.01499999... in IEEE 754, so rounding to 2 decimal places floors to $0.01 — a 33% understatement of the Team plan's per-conversation rate. This appears in the overage hint on line 228 when the Team plan is selected with extra conversations. maximumFractionDigits should be increased to 3 to accommodate this rate.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/agents/pricing-calculator.tsx
Line: 52-58

Comment:
**`formatCurrency` truncates Team overage rate to wrong value**

`maximumFractionDigits: 2` causes `formatCurrency(0.015)` to render as `$0.01` instead of `$0.015`. In JavaScript, `0.015` is stored as `0.01499999...` in IEEE 754, so rounding to 2 decimal places floors to `$0.01` — a 33% understatement of the Team plan's per-conversation rate. This appears in the overage hint on line 228 when the Team plan is selected with extra conversations. `maximumFractionDigits` should be increased to `3` to accommodate this rate.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Cursor

}

function formatNumber(value: number): string {
return new Intl.NumberFormat('en-US').format(value);
}

export function ConnectPricingCalculator(): React.ReactElement {
const [selectedPlanId, setSelectedPlanId] = useState<PlanId>('pro');
const [conversations, setConversations] = useState(1500);

const selectedPlan = PLANS.find((plan) => plan.id === selectedPlanId) ?? PLANS[1];

const estimate = useMemo(() => {
const extraConversations = Math.max(0, conversations - selectedPlan.includedConversations);
const overageCost =
selectedPlan.overageRate === null ? 0 : extraConversations * selectedPlan.overageRate;
const total = selectedPlan.monthlyPrice + overageCost;
const isOverFreeLimit =
selectedPlan.id === 'free' && conversations > selectedPlan.includedConversations;

return {
extraConversations,
overageCost,
total,
isOverFreeLimit,
};
}, [conversations, selectedPlan]);

function handleConversationsChange(value: number): void {
const nextValue = Math.min(Math.max(0, value), MAX_CONVERSATIONS);

setConversations(nextValue);
setSelectedPlanId((currentPlanId) => {
if (currentPlanId === 'free' && nextValue > FREE_TO_PRO_THRESHOLD) {
return 'pro';
}

if (
(currentPlanId === 'pro' || currentPlanId === 'team') &&
nextValue < PRO_TEAM_TO_FREE_THRESHOLD
) {
return 'free';
}
Comment on lines +91 to +101

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Auto-switching overrides manual Team plan selection unexpectedly

When a user explicitly clicks Team and then drags the slider below 100, the plan silently resets to Free. There's also a one-way threshold inconsistency: Free auto-promotes to Pro at 300 conversations but Pro/Team never auto-promotes to Team — a user on the Pro plan with 5,001 conversations (above the Team threshold) simply sees growing overage instead of being guided toward Team. Both behaviours could confuse users trying to compare plans.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/agents/pricing-calculator.tsx
Line: 91-101

Comment:
**Auto-switching overrides manual Team plan selection unexpectedly**

When a user explicitly clicks Team and then drags the slider below 100, the plan silently resets to Free. There's also a one-way threshold inconsistency: Free auto-promotes to Pro at 300 conversations but Pro/Team never auto-promotes to Team — a user on the Pro plan with 5,001 conversations (above the Team threshold) simply sees growing overage instead of being guided toward Team. Both behaviours could confuse users trying to compare plans.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor


return currentPlanId;
});
}

return (
<div className="not-prose my-6 rounded-lg border bg-fd-card p-5 shadow-sm">
<div className="mb-5">
<h3 className="text-base font-medium text-fd-foreground">Estimate your monthly cost</h3>
<p className="mt-1 text-sm text-fd-muted-foreground">
Pick a plan and set how many active conversations you expect each billing cycle.
</p>
</div>

<div className="mb-6">
<p className="mb-2 text-sm font-medium text-fd-foreground">Plan</p>
<div className="grid gap-2 sm:grid-cols-3">
{PLANS.map((plan) => {
const isSelected = plan.id === selectedPlanId;

return (
<button
key={plan.id}
type="button"
onClick={() => setSelectedPlanId(plan.id)}
className={cn(
'rounded-md border px-3 py-2 text-left transition-colors',
isSelected
? 'border-[#4b73ec] bg-[#f3f6ff] dark:border-[#00d5ff] dark:bg-[#0f1a24]'
: 'border-fd-border hover:border-fd-muted-foreground/40'
)}
>
<span className="block text-sm font-medium text-fd-foreground">{plan.name}</span>
<span className="block text-xs text-fd-muted-foreground">
{plan.monthlyPrice === 0
? '$0 / month'
: `${formatCurrency(plan.monthlyPrice)} / month`}
</span>
</button>
);
})}
</div>
</div>

<div className="mb-6">
<div className="mb-2 flex items-end justify-between gap-3">
<label
htmlFor="connect-pricing-conversations"
className="text-sm font-medium text-fd-foreground"
>
Active conversations per billing cycle
</label>
<input
id="connect-pricing-conversations-input"
type="number"
min={0}
max={MAX_CONVERSATIONS}
value={conversations}
onChange={(event) => handleConversationsChange(Number(event.target.value))}
className="w-28 rounded-md border border-fd-border bg-fd-background px-2 py-1 text-right text-sm text-fd-foreground"
/>
Comment on lines +148 to +162

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Number input has no accessible label

The <label> element's htmlFor="connect-pricing-conversations" points to the range slider, but the numeric <input> has id="connect-pricing-conversations-input" with no associated label. Screen readers will not announce the purpose of the number field when it receives focus.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/agents/pricing-calculator.tsx
Line: 148-162

Comment:
**Number input has no accessible label**

The `<label>` element's `htmlFor="connect-pricing-conversations"` points to the range slider, but the numeric `<input>` has `id="connect-pricing-conversations-input"` with no associated label. Screen readers will not announce the purpose of the number field when it receives focus.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Cursor

</div>
<input
id="connect-pricing-conversations"
type="range"
min={0}
max={MAX_CONVERSATIONS}
step={50}
value={conversations}
onChange={(event) => handleConversationsChange(Number(event.target.value))}
className="w-full accent-[#4b73ec] dark:accent-[#00d5ff]"
/>
<div className="mt-1 flex justify-between text-xs text-fd-muted-foreground">
<span>0</span>
<span>{formatNumber(MAX_CONVERSATIONS)}</span>
</div>
</div>

<div className="rounded-md border border-fd-border bg-fd-background p-4">
<dl className="space-y-2 text-sm">
<div className="flex items-center justify-between gap-4">
<dt className="text-fd-muted-foreground">Plan price</dt>
<dd className="font-medium text-fd-foreground">
{formatCurrency(selectedPlan.monthlyPrice)}
</dd>
</div>
<div className="flex items-center justify-between gap-4">
<dt className="text-fd-muted-foreground">Included conversations</dt>
<dd className="font-medium text-fd-foreground">
{formatNumber(selectedPlan.includedConversations)}
</dd>
</div>
<div className="flex items-center justify-between gap-4">
<dt className="text-fd-muted-foreground">Extra conversations</dt>
<dd className="font-medium text-fd-foreground">
{formatNumber(estimate.extraConversations)}
</dd>
</div>
<div className="flex items-center justify-between gap-4">
<dt className="text-fd-muted-foreground">Overage charges</dt>
<dd className="font-medium text-fd-foreground">
{selectedPlan.overageRate === null
? 'Not available on Free'
: formatCurrency(estimate.overageCost)}
</dd>
</div>
<div className="border-t border-fd-border pt-2">
<div className="flex items-center justify-between gap-4">
<dt className="font-medium text-fd-foreground">Estimated total</dt>
<dd className="text-lg font-semibold text-fd-foreground">
{formatCurrency(estimate.total)}
</dd>
</div>
</div>
</dl>

{estimate.isOverFreeLimit ? (
<p className="mt-3 text-sm text-fd-muted-foreground">
Free is capped at {formatNumber(selectedPlan.includedConversations)} active
conversations per billing cycle. Upgrade to Pro or Team to keep running, or wait for
your next cycle to reset.
</p>
) : null}

{selectedPlan.overageRate !== null && estimate.extraConversations > 0 ? (
<p className="mt-3 text-sm text-fd-muted-foreground">
Overage is billed at {formatCurrency(selectedPlan.overageRate)} per extra conversation
on the {selectedPlan.name} plan.
</p>
) : null}
</div>

<p className="mt-3 text-xs text-fd-muted-foreground">
Enterprise pricing is custom. For committed volume, HIPAA BAA, SSO, or SCIM, contact{' '}
<a href="mailto:sales@novu.co" className="text-fd-primary underline underline-offset-2">
sales@novu.co
</a>
.
</p>
</div>
);
}
Loading