ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

自研CRM实践:桌面优先与沟通即记录的客户管理之道

自研CRM实践:桌面优先与沟通即记录的客户管理之道 去年秋天我们团队还在过一种很原始的日子客户资料躺在共享表格里跟进记录散落在个人聊天窗口和邮箱里谁联系过谁、上次聊了什么基本靠记忆和翻聊天记录。某个老客户在聊天工具里问贵司上次那份报价还能不能执行我们三个人翻了半小时最后从一个离职同事遗留的网盘文件里找到了一份过期报价单。这种事情反复出现终于让人意识到再靠表格和聊天工具做客户跟单做大的同时一定翻车。我们决定自己动手写一套CRM最后做出来的东西就是后来团队内部天天在用的 DeskcommCRM。Deskcomm 这个名字来自 Desk Communication 的缩写翻译成本土语境就是桌面沟通。整个系统的核心理念只有一条把销售服务和客户沟通的每一个动作都放进办公桌面的工作流里并且自动沉淀成可检索、可回溯、可协作的客户档案。这篇文章不是什么号称颠覆行业的方案就是我们实际操作过程中的选型思路、数据设计、踩坑记录和上线后的真实反馈。如果你也在运营一支小规模的服务型或销售型团队有差不多的痛点可以参考这套做法。1. 我们为什么要自研了一套CRM而不是直接采购现成的1.1 散落的数据才是最大的管理成本团队六到八个人做的是企业软件实施和后续运维服务。这种业务的客户特点是数量不算大但每个客户的沟通频次高、链条长、参与角色多。同一个客户销售谈了一遍实施顾问跟了一遍客服又处理过几次工单。每次交接依赖口头说明和转发聊天记录信息损耗非常严重。从数据视角看客户资产的形态是一份共享表格记录公司名、联系人和大致金额邮件散落在各自的邮箱聊天记录在个人账号里工单系统又是一个独立的页面。数据之间没有任何主键关联同一个客户在不同系统里可能长得完全不一样。最痛的一次是一个客户续约前我们需要汇总过去一年跟对方的全部往来结果花了一个下午才拼出大致轮廓还漏了两封关键邮件差点把续约报价搞错。如果说销售团队最大的风险是客户流失那么在数据层最大的风险就是信息跟着人走而不是跟着客户走。人离职了记忆带走了聊天记录删了客户关系就断了一截。这个问题不是靠管理制度就能根治的必须有一个系统在操作层面强制沉淀。1.2 市面上的CRM要么太重要么数据在别人手里做采购调研的时候我们仔细评估过几类现成方案。一类是通用型CRM大厂产品功能确实覆盖全能建复杂流程但对我们六个人的团队来说配置周期和实施成本太高了而且销售团队普遍抵触被迫操作的系统一类是轻量的在线表格增强工具灵活确实灵活但在通信同步、离线使用、桌面通知这些场景上基本无能为力还有一类是依托大厂生态的客户管理工具最大的顾虑是数据主权——我们的客户档案和聊天记录一旦全部上去后续想批量导出来换平台难度会非常大。还有一个经常被忽略的问题很多CRM默认是浏览器里的一个后台系统销售要养成和客户聊完去后台补一条记录的习惯这在执行层面几乎一定会失败。人都是懒惰的只要录入步骤多一步这个步骤就会被跳过。真正好用的工具应该是让数据和记录发生在沟通发生的同一秒、同一个界面上。1.3 DeskcommCRM 的两条产品原则基于上面的痛点我们给这个项目定下了两条硬性原则后面所有技术决策都围绕它们展开。第一条叫做 Desk-first桌面优先。闹钟式地反复提醒自己要登录网页后台录数据不是我们的工作方式。我们希望在办公桌前完成聊天、看邮件、拨号、写跟进记录系统本身是那个只有我们几个在用的那台桌面设备上的常驻应用而不是一个需要主动想起的网页标签页。第二条叫做 Communication-native沟通即记录。每次对外沟通的产出物——邮件、聊天、通话摘要、会议结论——都应该以结构化的形式出现在客户档案里不需要二次复制粘贴。最开始很难做到完整自动化但至少要形成半自动快速补充的路径。事实证明只要把录入成本降低到一次点击或一句语音转文字团队就愿意配合。这两条原则虽然听着简单但它们决定了后续的很多技术选择。比如为什么我们优先做桌面客户端为什么数据模型里反复强调统一活动流又为什么同步协议上花了非常多的时间。2. 整体架构桌面客户端是大脑服务端是记忆2.1 技术选型Electron桌面端 NestJS服务端架构上我们最终选择了 Electron React 做桌面客户端NestJS PostgreSQL Redis 做服务端中间通过一套自定义的增量同步协议通信。这个组合可能不算最时髦但非常符合我们团队的技术栈和实际需求。考虑过纯Web方案吗考虑过但被否了。原因有三第一邮件和聊天是我们两个最重要的数据源桌面客户端在系统级集成这些协议时有天然优势IMAP长连接、本地通知、窗口失焦更新这些操作在浏览器里都会遇到各种限制第二销售在外网不稳定的时候也想查询客户资料纯网页模式断网即瘫痪而桌面客户端可以做本地缓存和离线读写第三桌面端的性能和感知更像工具视频会议、语音记录这类功能后续要挂进来桌面端更顺滑。后端选 NestJS 而不是 Express主要是看中它的模块化边界和依赖注入体系。CRM 这种项目业务实体之间关系复杂今天写了一个联系人模块明天就要加一个商机模块后天还要集成工单系统。如果服务端是一堆自由散装的 Express 路由到后期改一处数据模型会牵连出一堆隐性耦合。NestJS 提供的模块隔离、DTO校验、TypeORM集成虽然前期写起来稍微麻烦但后面扩展业务时的收益远远超过那点成本。2.2 本地优先与云端同步一个类似记账本的数据流数据流的设计上我们没有采用传统的客户端请求服务端响应模式而是采用类似的本地记账异步对账的方式。所有交互先写进本地 SQLite然后同步队列把变更异步发送到服务端服务端通过 WebSocket 把其他端产生的变更推送回来。这样做最大的好处是客户资料页面的任何操作在本地是瞬时完成的没有网络延迟的体感。我们做演示的时候经常被问你们这个为什么那么快其实不是快是没有等待网络往返。同步的单元不是整行数据而是变更操作。每当一次输入落地本地会生成一条类似下面这样的操作记录{ op: upsert, collection: interactions, id: 9b2c0d1e-3456-4f6a-8f8a-1a2b3c4d5e6f, fields: { contact_id: c1d2e3f4-aaaa-4b1a-8a2b-000000000001, channel: email, direction: inbound, content: { subject: 续约方案沟通, body_preview: 您好关于下一年度的服务合同... } }, server_ts: 1699876543210 }服务端收到后做两件事先校验权限和数据合法性然后写 PostgreSQL同时广播一个消息给同一工作区内的其他客户端让它们更新缓存。由于本地和市场之间的时钟可能存在偏差所有排序字段一律使用服务端下发的server_ts客户端只负责展示不参与跨设备排序决策。2.3 核心数据模型先画清楚客户档案的骨架数据模型是CRM的地基这里花的心思最多。我们的核心表设计其实不复杂但每张表的职责都要严格分清。先说五张核心表contacts联系人包括姓名、手机、邮箱、职位、归属负责人等基础字段。companies客户公司一个公司下面挂多个联系人。interactions沟通记录覆盖邮件、聊天、电话、线下会议四种类型是整个系统里增长最快的表。tasks待办事项例如本周内给XX客户回电确认需求绑定到具体联系人或公司。deals商机保存售前阶段的金额、阶段、预计成交时间。其中interactions表的结构非常关键。它不区分具体业务场景而是用一种统一的事件表承载所有沟通类型。字段上我们特别关注channel和direction两个枚举channel指渠道email/chat/call/meetingdirection指往来方向inbound/outbound。这样设计的目的是为了后面所有业务逻辑——搜索、客户360度视图、待办提醒——都只面向一张有规律的大表而不是每个渠道各写一套查询逻辑。CREATE TABLE interactions ( id UUID PRIMARY KEY, contact_id UUID NOT NULL REFERENCES contacts(id), company_id UUID REFERENCES companies(id), channel TEXT NOT NULL CHECK (channel IN (email,chat,call,meeting)), direction TEXT NOT NULL CHECK (direction IN (inbound,outbound)), content JSONB, occurred_at TIMESTAMPTZ NOT NULL, created_by UUID NOT NULL, is_deleted BOOLEAN DEFAULT FALSE );用一个JSONB字段承载不同渠道的具体内容灵活性很高。邮件时可以存正文摘要和附件列表聊天时可以存消息纯文本电话时可以存通话摘要和时长。关系型数据库的强约束负责关系完整性和查询JSONB负责灵活性两者结合对半结构化的沟通数据来说非常合适。2.4 统一活动流让客户360度变成一条自动生成的流水很多CRM系统的客户详情页是一堆独立板块联系人信息、商机列表、工单记录分别展示。我们的设计不一样客户详情页的中间主体是一张按时间线排列的活动流水所有类型的事件共用同一套视觉元素和时间轴。实现思路是建立一张物化视图——实际是一张activity_stream宽表每当 contacts、interactions、deals、tasks 产生变更系统就向这张表插入一条带动作类型和时间戳的记录。前端订阅活动流的变更收到新记录就自动滚动刷新不需要手动刷新页面。这个设计在真实使用中的效果出乎我们意料。团队的商务在客户详情页往下翻一遍就能在十秒钟内重新掌握这个客户的所有来龙去脉新接手的人不需要去问这个人以前聊天记录在谁那里。信息沉淀变成了系统自动完成的事情而不是某个人想起来才去做。这也是为什么后来团队对DeskcommCRM的接受度那么高——它省掉的不是一次录单操作而是很多次找人问历史的时间。3. 通信集成与沟通即记录的具体实现3.1 邮件通道IMAP增量同步与SMTP外发邮件是客户沟通的主渠道所以第一个做的集成就是邮件同步。这里开发的工作量比预想的多因为IMAP全量拉取是一个非常危险的起步动作。第一版同步逻辑写得很天真启动时拉取邮箱全部邮件然后每次都全量扫描一遍。结果第一次陈年邮件同步直接把办公室出口带宽打满工作区所有人上网卡了十分钟。后来改成三种机制配合首次同步只拉最近30天的邮件更早的历史邮件通过后台任务慢慢补。后续同步使用 IMAP 的增量拉取方式只拉取某次UID之后的邮件减少无效请求。队列限速每秒钟最多处理五封邮件的解析和写入防止邮件高频到达时把同步服务打死。// 使用 IMAP UID 增量拉取的核心逻辑简化示意) async function fetchIncrementalEmails(accountId, sinceUid) { const box await imapClient.openBox(INBOX, true); const results await box.search({ uid: ${sinceUid}:* }); for (const uid of results) { const msg await box.fetch(uid, { bodyStructure: true, source: true }); enqueueEmailParse(accountId, uid, msg); } return results.length 0 ? Math.max(...results) : sinceUid; }邮件发送这块我们直接把 SMTP 外发封装成了系统内的一个动作。以前的情况是销售收到邮件后要切到自己邮箱再回复这封回复邮件不会自动出现在客户档案里。现在客户详情页里直接有写邮件按钮发送时自动拉取联系人的地址和签名并且把发送记录写入 interactions 表。这个改动虽然很小但效果立竿见影操作路径短了行为记录也全了。3.2 聊天工具、电话记录和线下会议的归并聊天记录的沉淀是最麻烦的一块。我们没有办法把企业IM的个人聊天机器人接入做成全自动因为协议限制很多。最终采用的方案是折中而且有效的给每个客户建立一个群组标签团队内部约定在和客户沟通时至少把重要结论同步发到一个特定的归档文档或群机器人。系统订阅这些机器人的 Webhook 消息按群组标签自动匹配到对应客户写入 interactions。这样虽然做不到每个字都在系统里但所有重要结论至少不会丢。语音转文字我们调的是一个现成的ASR服务在系统里录一段通话摘要自动转成文字并归档比手动打字快很多。线下会议和拜访记录我们做的是扫码创建记录的功能每次拜访前系统生成一个二维码落地页上有客户名称、时间、参与人回来后自动创建一条会议型 interaction。销售不用打开电脑就能录记录率和以前比提升了不少。3.3 全局搜索与上下文面板的体验优化系统有了足够多的数据之后检索能力就变得格外重要。PostgreSQL 自带的全文检索已经能满足80%的搜索需求我们直接用tsvector做了联系人、公司、交互记录的统一索引。搜索范围是全局的输入一个客户名或关键词搜索结果按时间排序优先显示最近的交互记录。客户详情页右侧还有一个上下文面板这个设计借鉴了现代邮件客户端的思路。选中一条交互记录时面板会展示上下文信息客户所在公司的其他联系人、最近三条沟通记录、相关联的待办任务和商机。这个面板解决了一个很实际的问题看到一条旧邮件时不需要跳转多个页面才能想起来当时为什么发这封邮件。3.4 权限与审计小团队也不能完全不做六个人的团队确实不需要复杂的企业权限体系但有些底线必须守住。我们做了三个角色admin可以配置所有内容、导出全量数据owner只处理名下客户member可以查看权限范围内的客户资料。数据导出做了审计日志谁在什么时间导出了什么范围的客户数据都有记录。这一点在后续客户合规审查中给了我们不少底气。4. 开发和实测过程中踩过的坑4.1 邮件首轮同步直接把出口带宽打满前面提到的邮件全量同步风暴不是段子它真实发生在我们的第一个测试环境。测试机同步一个比较大的邮箱时CPU 和网络双双拉满整条办公网都受影响。当时排查的过程很有意思一开始以为是无线网络出了问题把路由器都重启了一遍后来用nload看实时流量才发现是同步服务在疯狂下载附件。这个教训直接催生了我们的同步系统三层防护原则任务限流、数据增量、附件按需下载。附件不随邮件正文一起同步只在用户主动打开邮件时按需拉取缓存有效期一周。这个改动不仅解决了带宽问题还显著降低了本地磁盘占用。4.2 SQLite 本地缓存的串行写入与 Electron 生命周期冲突Electron 主进程和渲染进程之间的关系是开发桌面应用绕不开的坎。我们第一版把 better-sqlite3 放在渲染进程里直接查询结果经常出现奇怪的死锁和database is locked错误。查到最后发现问题出在渲染进程的多个组件同时发起数据库写入better-sqlite3 虽然同步但多个JavaScript调用栈并发进入时冲突还是难免。后来我们把所有数据库访问全部收敛到主进程渲染进程通过preload暴露的桥接对象调用数据库方法。更关键的是所有写操作进入一个单一的 FIFO 队列同一时间只允许一个写事务在 SQLite 上执行。实践证明这个方案非常稳定再也没有出现过锁错误。Electron 生命周期还坑过一次用户点击关闭窗口时如果同步队列里还有待发送的数据窗口退出会把进程杀掉导致数据丢失。我们在before-quit事件里加了同步确认队列清空或用户明确选择退出并丢弃未同步变更时才真正退出。4.3 时间戳精度导致的跨设备排序翻转排序问题直到第二个客户端加入实测才暴露。两台电脑同时打开某个客户的活动流刷新后顺序不一致有时候一条新的聊天记录跑到了昨天的邮件下面。排查到最后原因非常朴素客户端本地生成时间戳用的是new Date()两台设备的本地时钟相差了大概四十秒而服务器没有纠正客户端上传的本地时间直接用这个时间排序。修复方案也简单——服务端接管所有排序时间。客户端任何记录入库时本地时间只作为辅助展示字段真正的排序一律使用服务端下发的server_ts一个单调递增的服务端毫秒时间戳。从那以后排序问题再没出现过。这件事也让我意识到分布式系统里永远不要把客户端时钟当作可信数据源。4.4 桌面打包与自动更新的细碎问题桌面应用分发和更新的坑比业务逻辑还要多。Windows 上我们用的 electron-updater 自动更新第一次发版时因为没有给 exe 加代码签名Windows SmartScreen 直接拦截同事们通过仍要运行才能装。高风险但不加签名主要影响体验后来弄了证书解决。Linux 下打包 AppImage 遇到的坑更冷门启动时报SUID sandbox helper binary was found, but is not configured correctly原因是chrome-sandbox可执行文件权限不够或者没有设置属主。这个问题网上解法很多但实际执行时还要注意应用目录只读权限的影响。总的来说桌面应用的分发复杂性是被低估的工作建议任何采用 Electron 方案的团队都提前把签名证书、自动更新通道、平台回归测试列入排期。5. 上线半年后的真实使用情况和后续扩展方向5.1 每天都在用的功能和预想的不太一样系统上线半年后我们拉过一次使用数据发现实际使用最频繁的功能和我们最初设想的优先级并不是完全一致。最高频的是三件事全局搜索、客户详情页的时间线、以及邮件发送。搜索解决的是我记得和这个人聊过某件事但不知道在哪里的痛点时间线解决的是新同事接手客户时快速了解背景的痛点邮件发送因为直接嵌入工作流省掉了太多切换成本。反观那些 CRM 经典功能——自定义报表、复杂的商机漏斗、自动化工作流——实际使用率很低。不是说它们没用而是在我们这个规模的团队里业务体量还远远到不了需要自动化规则的程度。与其把时间花在复杂的配置功能上不如先把数据采集做扎实。这个认知对我们后续迭代方向影响很大。5.2 团队接受度从被迫录到顺手记的转变内部工具落地最担心的就是大家不用。我们的经验是要让系统对使用者产生即时回报而不是单纯增加录入负担。举一个最简单的例子早期我们把录入当作义务来推动销售觉得是在为系统打工抵触心理很强。后来加入了智能提醒功能系统根据历史交互时间自动生成该跟进XX客户了的待办并且把客户最近状态直接展示在待办旁边。这个时候登录系统变得像是在获得信息而不是上报信息使用率立刻不一样了。还有一个细节我们允许成员对任意记录写自由文本评论这些评论会成为客户档案的一部分。这个设计让系统从一个强制登记簿变成了团队内部针对客户的协作讨论区大家在客户页面上直接讨论方案、同事确认价格反而减少了很多碎片化的群聊。5.3 后续计划商机漏斗、规则引擎与移动端轻量入口接下来我们打算在现有数据基础上把商机模块做得再深一些。商机漏斗的价值需要大量数据积累才能显现半年下来我们已经有几百条有效 interaction 记录足够做一些阶段转化率分析了。另外计划做一个轻量的规则引擎例如客户最近30天没有互动自动给负责人推送提醒或金额超过XX万的商机必须创建关联任务把一部分跟单动作从人工记忆里解放出来。移动端方面我们暂时不做完整App而是做一个只读的轻量Web入口让外出拜访时能快速查客户资料。数据同步协议已经具备了多端能力这个Web入口只需复用现有接口即可开发成本不会很高。回顾这次自研经历最大的收获不是技术栈上的积累而是明白了工具和流程的关系工具再强大如果它不能融进团队已有的工作习惯就一定会变成摆设。DeskcommCRM能活下来的真正原因是它在记录这件事上足够自动在查找这件事上足够快让团队养成了每一次沟通都顺手发生的归档习惯。如果你也在考虑给团队做类似的内部工具建议先花一周时间观察大家真正的工作流然后从最痛的那一环开始而不是上来就设计一个功能齐全的大系统。
返回列表