SlowPost 為激動訊息降温,並延至翌日 09:00 解鎖
SlowPost: A Post Office for Messages You Shouldn't Send Yet
作者打造開源工具 SlowPost,將激動草稿改寫為冷靜版本並暫存至翌日 09:00 解鎖,讓用戶再決定是否發送。工具亦提供中立摘要及雙方需求分析,並從 17 張緩和衝突技巧卡中檢索適用內容,支援改寫。作者以 12 條中文草稿評估檢索,Recall@3 由 54.2% 升至 75.0%;DeepSeek-V3.2 在模型比較中延遲最低(12.9s),熱度下降指標為 83%。
What I Built
SlowPost is a slow post office for heated messages.
We've all written that message: the 1 a.m. resignation letter, the reply-all
with a little too much honesty, the text to your roommate about the dishes that
somehow escalates into questioning their entire upbringing. You don't regret
writing it. You regret sending it.
SlowPost changes the workflow. Instead of sending the message, you mail it to
tomorrow:
Paste the draft exactly as angrily as you wrote it, plus a line of context
("to my roommate, about the dishes").
An open-weight model rewrites it into a calm version — same facts, same
request, none of the character assassination.
You also get a neutral third-party summary of the dispute and a breakdown
of what each side probably needs.
The letter is then held in the post office. It doesn't go anywhere. In
real mode it unlocks at 09:00 the next morning, when you can take out the
calm version and decide — with a rested brain — whether to send anything at all.
SlowPost is a sibling of my earlier project WeVi, a private AI mediation
space where two people talk and only share summaries both sides approve. WeVi
mediates conversations that are already happening; SlowPost catches the message
before it becomes a conversation you'll regret.
The open-source stack (and why it's the whole point)
A tool that stores your angriest, most private words has no business being
locked into one vendor's closed API. Every model in SlowPost is open-weight:
Role
Model
Rewrite + analysis
DeepSeek-V3.2 (open weights)
Embeddings
Qwen3-Embedding-8B
Rerank
Qwen3-Reranker-8B
They're served here through SiliconFlow's OpenAI-compatible API — but because
the models are open weights, the same code runs fully locally against Ollama
or vLLM by changing one environment variable. Hosted today, offline tomorrow,
self-hosted whenever: that choice belongs to the user, and it only exists
because the models are open.
How I Built It
Retrieval, not just vibes. SlowPost keeps a library of 17 de-escalation
technique cards — Nonviolent Communication, Gottman's "soft startup", repair
attempts, boundaries without threats, the overnight rule. For each draft it
runs hybrid retrieval (BM25 + embedding recall, merged with Reciprocal Rank
Fusion) and a cross-encoder rerank to pick the three techniques that fit
this specific conflict. Those cards are injected into the prompt, so the
rewrite is grounded in an explicit technique, not generic "be nicer" energy.
Strict structured output. The model must return JSON — calmRewrite,
neutralSummary, theirNeeds, myNeeds, techniquesUsed, riskFlags —
validated against a zod schema. Invalid output is retried once with the
validation error fed back.
The hold. Letters are stored with a release time (next morning, 09:00;
demo mode shortens it). The UI won't show the calm version until the hold
expires. Slowness is the feature.
I measured it instead of guessing
I built a small eval harness: 12 heated Chinese drafts (roommates, partners,
clients, landlords, a 1 a.m. breakup letter…), each labeled with the technique
cards a human would pick.
Retrieval Recall@3: 75.0% (first run: 54.2%) — strict scoring: each case
labels the two cards a human would pick, and near-miss cards (equally valid
techniques) count as misses. The first run's misses turned out to be mostly
my pipeline's fault, not the reranker's: I cut the candidate pool to 8
before reranking (the right card was often sitting at #9–14), and the
cards' written vocabulary didn't match how people actually type when angry.
Widening the pool, adding colloquial keywords to the cards, and phrasing the
rerank query as a task ("pick the techniques that fit this message") took
recall from 54.2% to 75.0%.
Generation, per model (schema success / avg latency / avg "heat reduction"
from a transparent lexicon metric that scores absolutes, insults, threats and
exclamation density before vs. after; latency measured from a remote dev box,
the in-region production server is faster):
Model
Schema success
Latency
Heat reduction
DeepSeek-V3.2
12/12 (100%)
12.9s
83%
DeepSeek-V4.1-Flash
12/12 (100%)
14.8s
72%
Qwen3.5-35B-A3B
9/12 (75%)*
97.2s
78%
DeepSeek-V3.2 won on every axis — fastest and coolest-headed — so it's the
default. The newest model (Qwen3.5-35B-A3B) was 7× slower because of its long
reasoning traces: a neat reminder that "latest flagship" ≠ "best fit" for a
small, well-scoped job.
- Qwen3.5's row was measured from the production server, and 3 of its 12 calls failed on provider errors (HTTP 503s and dropped connections) across repeated attempts from two networks — its latency and heat figures cover the 9 completed cases. Even on a faster network it lost by 7×. The eval ships in the repo (npm run eval) — run it, get your own numbers, pick your own default model. That's the open-source way. Demo Live demo: https://slowpost.wevi.top — the public demo runs in demo mode: the overnight hold is shortened to 3 minutes so you can watch a letter unlock. (Production mode holds letters until 09:00 the next morning.) Repo: https://github.com/awer123456/slowpost !SlowPost demo: a roommate rant about takeout boxes, cooled from heat 54 to 0 — calm rewrite, neutral summary, both sides' needs, and the technique cards used Code Full source: https://github.com/awer123456/slowpost — TypeScript + Fastify + zod, no database (letters live in a small JSON store). The technique-card library (data/cards.json), the prompts, and the eval harness are all in the repo. npm install cp .env.example .env # point it at any OpenAI-compatible endpoint serving open-weight models npm start # the post office npm run eval # reproduce every number in this post Every model is swappable by environment variable — run the eval and pick your own default. What I learned Retrieval quality is the product. My first instinct was to spend all the effort on the rewrite prompt. The eval disagreed: when the wrong technique cards were retrieved, even a perfect prompt produced generic advice. Adding the rerank stage changed which cards got picked for most drafts, and the rewrites immediately became more specific — a concrete rota for the roommate, a boundary script for the parents. Grounding beats eloquence. Newest ≠ best fit. I benchmarked three open-weight models expecting the newest flagship to win. DeepSeek-V3.2 — not the newest — was fastest and produced the coolest rewrites, while Qwen3.5-35B-A3B's long reasoning traces made it 8× slower for no quality gain. For a small, well-scoped job, a boring model with a strict schema beats a brilliant one thinking out loud. Define the metric before you argue about quality. "Is this rewrite good?" is a vibes question. Forcing myself to define heat — absolutes, insults, threats, exclamation density — as a dumb, transparent lexicon score made quality measurable and comparable across models. The metric is crude and I say so in the repo; a crude metric you can run beats a sophisticated opinion you can't. …and check your ruler before you blame the model. Midway through, a demo run showed a calm rewrite scoring hotter than the original draft (54 → 64). The rewrite was fine; the metric wasn't. It contained the word 垃圾 — which means both "trash" (as in "shall we take turns clearing the trash?") and "you're trash" — and one literal, innocent use maxed out the score. I fixed the lexicon (masking literal uses, replacing ambiguous single characters with actual insult phrases) and added a guard to the pipeline itself: if a rewrite doesn't measure cooler than the draft, it regenerates once and keeps the cooler version. The most dangerous bugs in an AI pipeline are the ones in your own measuring stick. Why Does Open Innovation Matter? SlowPost stores the angriest, most private words a person types all week. "A closed API is your only option" is the wrong default for that job — open weights are what let the user choose where those words are processed, inspect what processes them, and self-host the whole thing when they want to. Openness is also what made this build possible at all. In one weekend, one person benchmarked three open-weight models head-to-head, picked a default by measurement instead of marketing, and shipped — because every model in the stack can be swapped with an environment variable and re-evaluated with the harness in the repo. No contracts, no vendor lineup deciding for you. And it compounds after the hackathon: anyone can fork this, run the eval, beat my 75.0% recall, and publish their numbers. A closed tool gets users; an open one gets successors. Slow down. Send tomorrow.
來源:DEV Community · dev.to