Skip to content
Merged
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
14 changes: 14 additions & 0 deletions .changeset/2330-in-app-docs-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"hotcrm": minor
---

Clause-②: no
Metadata-protocol: none

**A Marketing Manual under Help, and five more module pages, each with a Chinese version.** The service, revenue and marketing modules now explain their own rules in the app:

- User Manual, Service: **Case Triage** (what the assistant reads when it triages a case, the order it weighs things in, and why setting the priority, escalating and closing stay with a person).
- Revenue Manual: **Products & Price Books** (what a product carries, how its price reaches a deal or quote line, why a sold product is retired rather than deleted, and what price books do today) and **Billing Hand-off** (the two moments a won deal or an activated contract is sent to your billing system, and what travels with it). The Revenue Manual's one group is now titled *Revenue* rather than *Quotes*.
- New **Marketing Manual**, opened from **Help** after the Revenue Manual: **Campaigns** (statuses, member statuses, what each number counts and what ROI includes) and **Campaign Automation** (when Enroll Members, Add to Campaign and the nightly auto-completion run, and what each refuses). The manual and its Help entry belong to the marketing module and leave with it.

The billing hand-off's two moments, the auto-completion time and the buttons the triage page names are checked against the compiled app, so a changed flow or a renamed button fails the build until the page follows.
4 changes: 4 additions & 0 deletions .github/markdown-link-check-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"pattern": "^\\./(crm_revenue_[a-z_]+\\.md)$",
"replacement": "{{BASEURL}}/src/revenue/docs/$1"
},
{
"pattern": "^\\./(crm_marketing_[a-z_]+\\.md)$",
"replacement": "{{BASEURL}}/src/marketing/docs/$1"
},
{
"pattern": "^\\./(crm_overview\\.md)$",
"replacement": "{{BASEURL}}/src/docs/$1"
Expand Down
2 changes: 1 addition & 1 deletion content/docs/getting-started/quick-tour.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Inside HotCRM, the left nav opens with two pinned items — **Home**, which is t
| **Marketing** | Campaigns |
| **Insights** | CRM Overview, Forecasts |
| **Reports** | Opportunities by Stage, Won Opportunities by Owner, Pipeline Coverage, Opportunity Funnel by Owner, Lead Engagement, Accounts by Industry & Type, Customer Churn Signals, Cases by Status and Priority, SLA Performance, Cases Opened by Day |
| **Help** | User Manual, Admin Manual, Revenue Manual |
| **Help** | User Manual, Admin Manual, Revenue Manual, Marketing Manual |

**Reports** and **Help** are collapsed when the app loads — **Service**, **Sales**, **My Work**, **Activity**, **Marketing** and **Insights** open themselves. Click **Reports** open before deciding a report isn't there. **Help** opens the in-app manuals; **Admin Manual** is shown to administrators only.

Expand Down
2 changes: 1 addition & 1 deletion content/docs/getting-started/quick-tour.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ description: HotCRM 的 5 分钟演练 —— 主页、你的第一条线索、
| **Marketing** | Campaigns |
| **Insights** | CRM Overview、Forecasts |
| **Reports** | Opportunities by Stage、Won Opportunities by Owner、Pipeline Coverage、Opportunity Funnel by Owner、Lead Engagement、Accounts by Industry & Type、Customer Churn Signals、Cases by Status and Priority、SLA Performance、Cases Opened by Day |
| **Help** | User Manual、Admin Manual、Revenue Manual |
| **Help** | User Manual、Admin Manual、Revenue Manual、Marketing Manual |

上表用的是源码里的英文 label,也就是英文界面上看到的名字。简体中文界面下,它们显示为语言包里的译名——例如 **Inbox** 显示为「待我审批」、**Knowledge** 显示为「知识库」、**My Work** 显示为「我的工作」。

Expand Down
2 changes: 1 addition & 1 deletion content/docs/getting-started/quick-tour.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ description: HotCRM 的 5 分鐘演練 —— 主頁、你的第一條線索、
| **Marketing** | Campaigns |
| **Insights** | CRM Overview、Forecasts |
| **Reports** | Opportunities by Stage、Won Opportunities by Owner、Pipeline Coverage、Opportunity Funnel by Owner、Lead Engagement、Accounts by Industry & Type、Customer Churn Signals、Cases by Status and Priority、SLA Performance、Cases Opened by Day |
| **Help** | User Manual、Admin Manual、Revenue Manual |
| **Help** | User Manual、Admin Manual、Revenue Manual、Marketing Manual |

上表用的是原始碼裡的英文 label,也就是英文介面上看到的名字。本應用提供 en / zh-CN / ja-JP / es-ES 四種介面語言,沒有繁體語言包——簡體中文介面下這些名字會顯示為語言包裡的譯名,例如 **Inbox** 顯示為「待我审批」、**Knowledge** 顯示為「知识库」、**My Work** 顯示為「我的工作」。

Expand Down
23 changes: 23 additions & 0 deletions src/marketing/books/crm-marketing-manual.book.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { defineBook } from '@objectstack/spec';

/**
* The Marketing Manual (#2330) — the marketing module's own pages, from
* `src/marketing/docs/`. Built as the Revenue Manual is
* (`src/revenue/books/crm-revenue-manual.book.ts`): a book of this package,
* opened from *Help* by the entry `src/marketing/index.ts` contributes, so
* the book, its entry and its pages leave together when the module is
* uninstalled. The app's pages never link here; these pages may link to the
* app's.
*/
export const CrmMarketingManual = defineBook({
name: 'crm_marketing_manual',
label: 'HotCRM Marketing Manual',
description: 'Campaigns: what the numbers count, and what runs on its own.',
icon: 'megaphone',
order: 4,
groups: [
{ key: 'marketing', label: 'Campaigns', order: 1, include: 'crm_marketing_*' },
],
});
8 changes: 8 additions & 0 deletions src/marketing/books/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Doc Book Barrel (ADR-0046 §6) — the marketing module's table of contents
* over its `src/marketing/docs/*.md` pages.
*/

export { CrmMarketingManual } from './crm-marketing-manual.book';
86 changes: 86 additions & 0 deletions src/marketing/docs/crm_marketing_automation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
title: Campaign Automation — Enrolment & Auto-Completion
description: When each campaign flow runs, what it does, what it refuses, and what is kept up to date for you.
order: 2
# sources: the flows this doc documents. The completion schedule is guarded by
# test/docs-drift.test.ts; build ignores unknown frontmatter keys.
sources:
- flow:campaign_enrollment
- flow:campaign_lead_member_enroll
- flow:campaign_contact_member_enroll
- flow:campaign_completion
---

# Campaign Automation — Enrolment & Auto-Completion

For marketers and the administrators who support them. Three things put people
on campaigns or close campaigns. Two are buttons a person presses; one runs on
a schedule. None of them sends an email: a campaign records who it reached, and
the sending happens in your email tool.

## Enroll Members — a whole segment at once

**When it runs:** when you press **Enroll Members** on a campaign. The button is
offered while the campaign is **Planning** or **In Progress**, and only to
people who may add campaign members (Marketing User and the administrators). No
schedule runs it: the segment is chosen on its screen, and a scheduled run would
have nobody to choose it.

**What it does:** its screen asks two things. First whether to enroll **leads**
or **contacts** (one side per run), then the segment on that side: leads by
status (*New*, *Contacted* or *Qualified*), contacts by department. It then adds
everyone in that segment who has an email address and has not opted out of
email; a lead must also not be converted already, because a converted lead is
now a contact. Each new member starts at **Sent**.

**What it skips and refuses:** anyone already on the campaign is skipped, so
running it again tops the campaign up and never counts a person twice. If the
campaign is no longer open, nobody is added and the run says so. If anyone in
the segment is refused (for example, by permission), the run ends with both
counts, how many were enrolled and how many were refused, and the refused
people are not on the campaign.

## Add to Campaign — the people you picked

**When it runs:** when you run **Add to Campaign** on leads or contacts: on
**All Leads** or **All Contacts**, tick rows and run it from the selection
toolbar, or run it on one row from that row's menu. You pick the campaign. It
is offered to the same people as **Enroll Members**.

**What it does:** adds each person you picked as a member at **Sent**, one by
one. Someone already on that campaign is skipped, and the run still reports
success for them.

**What it refuses:** a campaign that is not **Planning** or **In Progress**, and
a person who has opted out of email. Nobody is added, the campaign's numbers do
not move, and the message names the campaign or the person. The same two
refusals apply however else a member is created: **New** on a campaign's
member list, an import, or the API.

## Campaign Auto-Completion — every night

**When it runs:** every day at **02:00**, where the deployment has switched
scheduled work on. A default install boots it unbound: there, a campaign stays
**In Progress** past its **End Date** until someone changes it, so complete it
yourself.

**What it does:** sets every **In Progress** campaign whose **End Date** has
passed to **Completed**. It changes the status and nothing else; the campaign
takes no new members from then on.

## Kept up to date for you

These are not flows you run, and they run on every install:

- **The campaign's numbers** are recomputed whenever a member is added, answers
or is removed, whenever one of its leads is converted, and whenever a deal
that names the campaign is saved or deleted. What each number counts is in
[Campaigns](./crm_marketing_campaigns.md).
- **Converted members.** When a lead is converted, each of its memberships at
**Sent** or **Responded** becomes **Converted**.
- **Opt-outs.** Setting a member to **Unsubscribed** ticks **Email Opt Out** on
that lead or contact, so both buttons above leave them out from then on.

---

**Related:** how a lead is converted — see [Leads](./crm_sales_leads.md).
42 changes: 42 additions & 0 deletions src/marketing/docs/crm_marketing_automation.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: 营销活动自动化——加入成员与自动完成
description: 每个营销活动流程何时运行、做什么、拒绝什么,以及哪些内容会自动保持最新。
---

# 营销活动自动化——加入成员与自动完成

写给市场人员和为他们提供支持的管理员。有三样东西会把人加入营销活动或结束营销活动。其中两样是由人点击的按钮,一样按计划自动运行。它们都不发送邮件:营销活动记录的是触达了谁,发送在你的邮件工具里完成。

## 批量加入成员——一次加入整个细分人群

**何时运行:**在营销活动上点击**批量加入成员**时。只有活动处于**规划中**或**进行中**时才提供这个按钮,而且只提供给可以添加营销活动成员的人(**市场专员**和管理员)。没有任何计划任务会运行它:细分人群要在它的界面上选择,计划任务运行时没有人来选。

**它做什么:**它的界面先问两件事。一是加入**线索**还是**联系人**(每次运行只加入一种),二是这一种里的细分人群:线索按状态(*新建*、*已联系*或*已确认*),联系人按部门。然后它把这个细分人群中有邮箱、且没有退订邮件的人全部加入;线索还必须尚未转化,因为已转化的线索现在是联系人。每个新成员都从**已发送**开始。

**它跳过和拒绝什么:**已经在活动中的人会被跳过,所以再运行一次只会补齐,绝不会把同一个人算两次。活动已不在可加入成员的状态时,不会加入任何人,运行结果会说明这一点。细分人群中如果有人被拒绝(例如因为权限),运行结束时会给出两个数字:加入了多少人、拒绝了多少人;被拒绝的人不在活动中。

## 加入营销活动——加入你选中的人

**何时运行:**在线索或联系人上运行**加入营销活动**时:在**全部线索**或**全部联系人**中勾选若干行,从选择工具栏运行;或者在某一行的菜单里对这一行运行。营销活动由你挑选。它提供给与**批量加入成员**相同的人。

**它做什么:**把你选中的每个人逐一加入为**已发送**状态的成员。已经在该活动中的人会被跳过,运行仍对他们报告成功。

**它拒绝什么:**不处于**规划中**或**进行中**的营销活动,以及已退订邮件的人。这时不会加入任何人,活动的数字也不会变化,提示会写明是哪个活动或哪个人。无论成员以其他什么方式创建,这两条拒绝都同样适用:活动成员列表上的**新建**、导入,或 API。

## 营销活动自动完成——每天夜间

**何时运行:**在已开启计划任务的部署中,每天 **02:00**。默认安装启动时不绑定它:在那里,过了**结束日期**的活动会一直保持**进行中**,直到有人修改,所以请自己把它设为已完成。

**它做什么:**把**结束日期**已过的每个**进行中**活动设为**已完成**。它只改状态,不改别的;从那以后活动不再加入新成员。

## 自动为你保持最新

这些不是由你运行的流程,在每个安装中都会运行:

- **营销活动的数字**会在以下情况重算:成员加入、回应或被移除,活动中的某条线索转化,以及关联了该活动的商机被保存或删除。每个数字统计什么,见[营销活动](./crm_marketing_campaigns.md)。
- **已转化的成员。** 线索转化时,它处于**已发送**或**已响应**的每条成员记录都会变为**已转化**。
- **退订。** 把成员设为**已退订**,会在对应线索上勾选**拒收邮件**(联系人上为**拒绝邮件**),之后上面两个按钮都会把这个人排除在外。

---

**相关:** 线索如何转化——见[线索](./crm_sales_leads.md)。
91 changes: 91 additions & 0 deletions src/marketing/docs/crm_marketing_campaigns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
title: Campaigns — Members, Responses & ROI
description: A campaign's status, what each member status means, what every number on the campaign counts, and what ROI includes.
order: 1
# sources: the objects this doc documents. Build ignores unknown frontmatter keys.
sources:
- object:crm_campaign
- object:crm_campaign_member
---

# Campaigns — Members, Responses & ROI

For marketers, and for the sales team that reads what marketing is running. A
campaign is one marketing effort (an email, a webinar, a trade show) with a
budget, a run of dates and a list of the people it reached. This page explains
what its numbers count, so you can trust them or know when not to.

## A campaign's status

| Status | What it means |
| --- | --- |
| **Planning** | Being prepared. It can already take members. |
| **In Progress** | Running. It takes members, and their answers are recorded. |
| **Completed** | Over. It takes no new members, so its member counts stay as they closed; a deal that names it and is won later still adds to its revenue. |
| **Aborted** | Called off before it finished. It takes no new members. |

An **In Progress** campaign whose **End Date** has passed is set to
**Completed** for you: see [Campaign Automation](./crm_marketing_automation.md).
You can also complete one yourself when it ends early.

Two rules hold on every save: **End Date** must be after **Start Date**, and
both are required. An **Actual Cost** above the **Budgeted Cost** does not stop
a save; it is there for you to notice.

## Members and their status

A **campaign member** is one person on one campaign: either a lead or a
contact, never both, and only once per campaign. A person enrolled once as a
lead and once as a contact is two memberships. How people get onto a campaign
is in [Campaign Automation](./crm_marketing_automation.md).

| Member status | What it means | What sets it |
| --- | --- | --- |
| **Sent** | The campaign reached them. Every new member starts here. | Enrolment |
| **Responded** | They answered: replied, registered, downloaded. | **Mark Responded** on the member, by someone who may edit members |
| **Converted** | Their lead became a customer. | Automatic, when that lead is converted |
| **Unsubscribed** | They asked to stop hearing from you. | Someone who may edit members, on the member |

Most members stay at **Sent**. A member at **Sent** or **Responded** whose lead
is converted becomes **Converted** for you; an **Unsubscribed** member never
does. Setting a member to **Unsubscribed** also ticks **Email Opt Out** on that
lead or contact, which keeps them out of every later campaign.

HotCRM does not track opens, clicks or bounces. What it can observe is a person
answering and a lead converting, and those are the statuses it has.

## What the numbers count

The numbers sit on the campaign itself, and they are kept current as members
are added, answer or leave, as their leads convert, and as deals name the
campaign. A running campaign shows real figures; you do not wait for it to end.

- **Number Sent** — every member of the campaign, whatever their status.
- **Number of Responses** — members at **Responded** or **Converted**: a member
whose lead converted answered too.
- **Response Rate %** — responses as a share of the number sent.
- **Number of Leads** — the different leads among the members.
- **Converted Leads** — how many of those leads have been converted.
- **Opportunities Created** — deals whose **Campaign** is this campaign.
- **Won Opportunities** — how many of those deals are **Closed Won**.
- **Actual Revenue** — the amounts of those won deals, added up.

## What ROI counts

**ROI %** is (**Actual Revenue** − **Actual Cost**) ÷ **Actual Cost**, as a
percentage. Three things follow from that:

- **Only won deals count.** Open pipeline is never added to **Actual Revenue**,
however promising.
- **Only deals that name the campaign count.** A deal is credited to a campaign
by its **Campaign** field, one campaign per deal. Set it on the deal: nothing
fills it in for you, converting a lead included, and a deal without it earns
the campaign nothing.
- **ROI is 0 until you enter a cost.** With no **Actual Cost**, there is
nothing to measure a return against, and **ROI %** reads 0. Enter the cost as
soon as you know it.

---

**Related:** how a deal moves to **Closed Won** — see
[Opportunities](./crm_sales_opportunities.md).
Loading
Loading