I built an email scorer in one HTML file with zero dependencies. Here is every threshold and where it came from.

I built an email scorer in one HTML file with zero dependencies. Here is every threshold and where it came from.

我用一个 HTML 文件构建了一个零依赖的邮件评分器。以下是每一个阈值的设定及其来源。

I built an email scorer in one HTML file with zero dependencies. Here is every threshold and where it came from. I write about B2B sales for a living, which means I read a lot of cold emails and a lot of advice about cold emails. The advice is almost always unfalsifiable. “Keep it short.” How short? “Be personal.” Measured how? So I turned the advice into numbers and put the numbers in a file. One index.html, about 220 lines, no framework, no build step, no API call. You paste a subject line and a body, it returns a score out of 100 across eight checks. Live version: Proposal Email Scorer. Source: github.com/leaderr-dev/proposal-email-scorer, MIT.

我用一个 HTML 文件构建了一个零依赖的邮件评分器。以下是每一个阈值的设定及其来源。我以撰写 B2B 销售内容为生,这意味着我阅读过大量的冷邮件以及关于冷邮件的建议。这些建议几乎总是无法证伪的。“保持简短。”多短算短?“要个性化。”怎么衡量?所以我将这些建议转化为数字,并把它们放入一个文件中。一个 index.html,大约 220 行代码,没有框架,没有构建步骤,没有 API 调用。你只需粘贴主题行和正文,它就会通过八项检查返回一个 100 分制的评分。在线版本:Proposal Email Scorer。源码:github.com/leaderr-dev/proposal-email-scorer,采用 MIT 协议。

The interesting part is not the UI, it is deciding what is measurable. Here is the whole thing. The constraint: everything deterministic. The obvious way to build this in 2026 is to send the email to a model and ask for a score. I did not want that, for three reasons. The first is that the same input has to produce the same output. If you paste an email, tweak one word and paste it again, a two point move should mean something. A model gives you a different number on the same input and you learn nothing. The second is privacy. People paste real emails about real deals into a tool like this. If there is no network call, there is nothing to explain. The third is that it made me define the thresholds instead of hiding behind a model. Every number below is a decision I had to justify.

有趣的部分不在于 UI,而在于决定什么是可衡量的。这就是整个项目的核心。约束条件是:一切必须是确定性的。在 2026 年,构建这个工具最显而易见的方法是将邮件发送给模型并要求评分。但我不想这样做,原因有三。首先,相同的输入必须产生相同的输出。如果你粘贴一封邮件,修改一个词后再粘贴一次,分数的变动应该是有意义的。而模型对相同的输入会给出不同的数字,你从中什么也学不到。其次是隐私。人们会将关于真实交易的真实邮件粘贴到这样的工具中。如果没有网络调用,就不存在隐私泄露的风险。第三,这迫使我定义阈值,而不是躲在模型背后。下面每一个数字都是我必须证明其合理性的决策。

Subject length, 28 to 55 characters

主题长度,28 到 55 个字符

Characters, not words. Mobile clients truncate somewhere around 40 to 55 depending on the device, so 55 is the ceiling. The 28 floor is the softer claim: short subjects correlate with automation because automated subjects are short. if (subj.length >= 28 && subj.length <= 55) { /* 15 points */ } else if (subj.length < 28) { /* 8 points */ } else { /* 6 points */ } Over-length scores lower than under-length, because a truncated subject actively hides information.

是字符,不是单词。移动客户端通常会在 40 到 55 个字符左右截断,具体取决于设备,所以 55 是上限。28 是下限,这是一个较弱的论点:短主题往往与自动化相关,因为自动生成的邮件主题通常很短。 if (subj.length >= 28 && subj.length <= 55) { /* 15 分 */ } else if (subj.length < 28) { /* 8 分 */ } else { /* 6 分 */ } 超长主题的得分比过短主题更低,因为被截断的主题会主动隐藏信息。

All caps and exclamation marks

全大写和感叹号

var caps = (subj.match(/\b[A-Z]{3,}\b/g) || []).length; var bangs = (subj.match(/[!]/g) || []).length; The {3,} matters. Without it, “SDR”, “CRM”, “B2B” and every other sales acronym flags. Three-plus-letter runs still catch acronyms, which is a known false positive I decided to live with, because a subject line stuffed with acronyms is its own problem.

var caps = (subj.match(/\b[A-Z]{3,}\b/g) || []).length; var bangs = (subj.match(/[!]/g) || []).length; {3,} 很关键。如果没有它,“SDR”、“CRM”、“B2B”以及其他所有销售缩写都会被标记。三个字母以上的连续大写仍然会捕捉到缩写,这是一个已知的误报,但我决定接受它,因为充斥着缩写的主题行本身就是一个问题。

Spam phrases

垃圾邮件短语

A 67 entry list, matched as substrings against subject and body joined together: var low = " " + t.toLowerCase() + " "; for (var i = 0; i < SPAM.length; i++) { if (low.indexOf(SPAM[i]) > -1) hits.push(SPAM[i]); } Scoring is banded rather than linear: zero hits is full marks, one or two is half, three or more is zero. That reflects how filters actually behave. One flagged phrase in an otherwise normal message is noise. Four is a pattern. The uncomfortable finding while assembling the list is how much of it is ordinary polite sales writing. “No obligation.” “Risk free.” “Limited time.” These are not things spammers say and salespeople avoid. They are things salespeople say constantly.

这是一个包含 67 个条目的列表,作为子字符串与合并后的主题和正文进行匹配: var low = " " + t.toLowerCase() + " "; for (var i = 0; i < SPAM.length; i++) { if (low.indexOf(SPAM[i]) > -1) hits.push(SPAM[i]); } 评分是分段的而非线性的:零命中得满分,一到两次命中得一半分,三次或以上得零分。这反映了过滤器实际的工作方式。在正常的邮件中出现一个被标记的短语可能是噪音,但出现四个就构成了模式。在整理这个列表时,一个令人不安的发现是,其中很大一部分竟然是普通且礼貌的销售用语。“无义务”、“无风险”、“限时”。这些并不是只有垃圾邮件发送者才说、销售人员会避免使用的词,而是销售人员经常挂在嘴边的词。

Body length, 50 to 150 words

正文长度,50 到 150 个单词

Under 50 there is not enough to say yes to. Over 150 reply rates fall and keep falling. Over 250 scores near zero.

少于 50 个单词不足以让人做出肯定的回应。超过 150 个单词,回复率会持续下降。超过 250 个单词,得分接近于零。

Reading grade

阅读难度等级

Standard Flesch Kincaid: 0.39 * (words / sentences) + 11.8 * (syllables / words) - 15.59. Syllable counting is where this gets approximate. The heuristic: function syl(w) { ... } It strips silent trailing e, es and ed, then counts vowel groups. It gets “business” and “meeting” right and “queue” wrong. For a whole email the errors wash out, which is why the tool reports a grade to one decimal and not a certified score. Target is 8 or below.

标准的 Flesch Kincaid 公式:0.39 * (单词数 / 句子数) + 11.8 * (音节数 / 单词数) - 15.59。音节计数是该算法近似的地方。启发式函数如下: function syl(w) { ... } 它会去掉末尾不发音的 e、es 和 ed,然后计算元音组。它能正确处理“business”和“meeting”,但会误判“queue”。对于整封邮件来说,这些误差会相互抵消,这就是为什么该工具报告的等级精确到小数点后一位,而不是一个认证分数。目标是 8 级或以下。

Call to action

行动号召 (CTA)

This is the check I am least happy with, because it is keyword matching: var CTA = ["are you open", "worth a", "would you be open", "do you have time", "15 minutes", "quick call", ...]; Plus a fallback: if the body ends in a question mark, that counts. It catches most real asks and it will miss an unusually phrased one. The alternative was a model call, which breaks the determinism rule. Keyword matching with a documented blind spot beat a black box with none.

这是我最不满意的一项检查,因为它基于关键词匹配: var CTA = ["are you open", "worth a", "would you be open", "do you have time", "15 minutes", "quick call", ...]; 此外还有一个兜底方案:如果正文以问号结尾,也算作 CTA。它能捕捉到大多数真实的请求,但会漏掉措辞不寻常的请求。另一种选择是调用模型,但这会破坏确定性规则。带有已知盲点的关键词匹配胜过一个完全不可知的黑盒。

Personalisation

个性化

Escape the company name, count occurrences: new RegExp(co.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g") The escape is not optional. Real company names contain dots and plus signs, and an unescaped . matches any character, so “A.B.” would match “Abx”. This check only tells you the name is present. It cannot tell you whether what surrounds it is specific or a mail merge compliment. That is the limit of a rule based tool, and it is the check that matters most, which is the honest tension in the whole project.

转义公司名称,计算出现次数: new RegExp(co.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g") 转义是必须的。真实的公司名称包含点号和加号,而未转义的“.”会匹配任何字符,因此“A.B.”会匹配到“Abx”。这项检查只能告诉你名称是否存在,无法判断其周围的内容是具体的还是邮件合并生成的客套话。这就是基于规则工具的局限性,也是最重要的一项检查,这正是整个项目中最真实的矛盾所在。

You to us ratio

“你”与“我们”的比例

Count you|your|yours against i|we|our|us|my. Second person should at least match first person. Five points, the lowest weight, because it is a diagnostic rather than a rule. A bad ratio means the email leads with what your company does, and fixing it means rewriting the argument rather than swapping pronouns.

计算 you|your|yours 与 i|we|our|us|my 的比例。第二人称的出现频率至少应与第一人称持平。五分,权重最低,因为它是一个诊断指标而非硬性规则。比例不佳意味着邮件以“你的公司做什么”为导向,要修复它需要重写论点,而不是简单地替换代词。

Weights

权重

Spam phrases 20, subject length 15, body length 15, CTA 15, subject shouting 10, reading grade 10, personalisation 10, ratio 5. Above 80 send, 60 to 79 fix something first, below 60 do not send.

垃圾邮件短语 20,主题长度 15,正文长度 15,CTA 15,主题大写 10,阅读难度 10,个性化 10,比例 5。80 分以上发送,60 到 79 分先修改,60 分以下不要发送。

What it deliberately does not do

它刻意不做的部分

It does not check DNS records, warm-up state or sending reputation, which matter more than any of the above and cannot be checked from a pasted string. It does not judge whether your offer is any good. And it will not write the email. For that last part I use the leaderr.io proposal generator, which takes your site and the prospect’s site and drafts the email, and the leaderr.io dossier generator for the company research that makes check seven mean something. Full disclosure, I write for Leaderr, which is also why I had a stack of proposal emails.

它不会检查 DNS 记录、预热状态或发送信誉,这些比上述任何一点都重要,且无法通过粘贴的字符串来检查。它不会判断你的报价是否优秀,也不会帮你写邮件。对于最后一点,我使用 leaderr.io 的提案生成器,它会获取你和潜在客户的网站并起草邮件;我还使用 leaderr.io 的档案生成器进行公司研究,这使得第七项检查(个性化)变得有意义。顺便披露一下,我为 Leaderr 工作,这也是我手头有一堆提案邮件的原因。