Go to file
anonsaber bfe0fd88aa feat: Telegram /search 邮件搜索(email/query Filter + SearchSnippet/get 高亮)
⑥-c 落地代码缺口:/search <关键词>

实现
- JmapBackend::search_emails 加 trait 默认实现(mock 后端返回 InvalidRequest,
  真机后端用 Email/query + text Filter 取 ID,再 SearchSnippet/get 取高亮)
- JmapService::search_emails 暴露搜索入口,与 list_emails_page 共享超时/错误映射
- worker:Intent::Search + /search 分发分支 + /help 说明行

降级路径(不 panic、不回栈给用户)
- SearchSnippet/get 不支持(unknownMethod/timeout)→ 仍返回匹配 ID 列表,
  渲染降级为高亮片段暂不可用
- 无匹配 → 没有找到匹配「…」的邮件。
- JMAP 侧失败 → SearchReply::Retry,交由协调器 503 + Retry-After 重试
- 搜索关键词为空 → 引导提示,不发请求

安全边界
- subject/preview 只做展示:strip_mark_tags 剥 <mark> 高亮(大小写不敏感、
  未闭合标记丢弃,不泄漏原始标记),unescape_html_entities 单趟解码,
  先剥标签再解码实体以保留邮箱正文里字面 <mark>
- 无匹配 ID / 主题 / preview 分别有截断上限(120/120/160 字符)
- 每条命中带 email_id,可直接接 /summary <email_id>
- 中文关键词仅前缀匹配,避免帮我搜一下发票被劫持成搜索、或覆盖授权意图

测试 +7(55 → 62 passed, 0 failed, 1 ignored)
2026-09-28 21:09:06 +08:00
.github/workflows feat: initial MessageWeave release 2026-09-25 09:14:04 +08:00
cloudflare-worker worker: /reconcile 走 per-route 长超时且绝不故障转移(01a0e595) 2026-09-28 12:18:04 +08:00
docs docs: ④ /debug/* 配套文档 + 全量锚点重锚 + AGENTS v3 版本基线(01a0e6de) 2026-09-28 16:20:18 +08:00
src feat: Telegram /search 邮件搜索(email/query Filter + SearchSnippet/get 高亮) 2026-09-28 21:09:06 +08:00
web feat(web): add SPA i18n with browser-language detection (zh/en, default en) 2026-09-27 12:53:36 +08:00
.dockerignore feat: initial MessageWeave release 2026-09-25 09:14:04 +08:00
.env.example refactor: rename project to MessageWeave 2026-09-25 10:29:18 +08:00
.gitignore chore(worker): rename proxy to lb and ignore build cache 2026-09-27 12:52:55 +08:00
AGENTS.md docs: AGENTS.md v3 基线文档哈希更正为 399b269(⑤ 收口提交) 2026-09-28 16:37:47 +08:00
Cargo.lock fix(redis): install explicit rustls crypto provider 2026-09-25 18:02:21 +08:00
Cargo.toml fix(redis): install explicit rustls crypto provider 2026-09-25 18:02:21 +08:00
Dockerfile chore(worker): rename proxy to lb and ignore build cache 2026-09-27 12:52:55 +08:00
README.md docs: README 中英双版补「保持 debug 关闭」的运维规则 2026-09-28 17:18:59 +08:00
README.zh-CN.md docs: README 中英双版补「保持 debug 关闭」的运维规则 2026-09-28 17:18:59 +08:00
hoststack.yaml docs(hoststack): add native Rust deployment config 2026-09-25 18:51:36 +08:00

README.md

MessageWeave

中文文档 / Chinese documentation → README.zh-CN.md

One Telegram bot per mailbox. New mail arrives as metadata; the original is always fetched read-only from your own JMAP server.

1. What it is

MessageWeave sits between a JMAP mail server (for example Stalwart) and a Telegram bot.

  • Inbound. A JMAP push subscription tells the service that a message changed. Nothing is polled at rest.
  • Outbound. Each event becomes a short, metadata-only Telegram notification: sender, subject, timestamp. Mail bodies are never sent to Telegram.
  • On demand. When you ask, the service reads the original through a fresh JMAP read call. Nothing is cached in between.
  • Optionally summarised. If you explicitly opt in, an OpenAI-compatible LLM returns a short summary. Requests, responses and attachment bytes are never persisted.

State is an architectural boundary here, not a preference.

  • Redis is the only state store. No database, no embedded store, no second store.
  • The process writes nothing to disk. No log files, no data files, no local volume.
  • No long-lived connections. No SSE, no WebSocket, no long polling.
  • Logs go to stdout only. The platform's collector owns log storage and retention.
  • What it is not. It is not a mail client, not a proxy, and not a relay. It forwards metadata to one bot; it does not read arbitrary URLs, open tunnels, or broker traffic.

2. 5-minute setup

Requires a reachable Redis 7 instance and a JMAP session plus a Telegram bot token.

cp .env.example .env           # fill REDIS_URL and CONFIG_ENCRYPTION_KEY
docker build -t messageweave:latest .
docker run --env-file .env -p 8080:8080 messageweave:latest

Then open http://localhost:8080 in a browser. The first-boot SPA calls POST /api/bootstrap, stores the business secrets in Redis, and creates the admin session. From that point the browser configures the service through Redis — no environment variable is read or written again for business configuration.

For local builds without Docker: cargo build --locked produces the message-weave binary.

If the service answers requests but does nothing useful, check the logs for a startup warning: a missing required variable does not crash the process. It serves a read-only setup router over in-memory state instead. See §3.

3. Configuration entry points

Three layers. They are not interchangeable.

Boot-time environment — 2 required, 2 optional.

Variable Required Default
REDIS_URL yes —
CONFIG_ENCRYPTION_KEY yes —
PORT no 8080
RUN_MODE no webhook

RUN_MODE accepts webhook or reconcile. .env.example ships only the two required variables; the rest are documented defaults.

Business configuration — Redis, written by the browser. POST /api/bootstrap is the one-shot trust bootstrap; PUT /api/business-config hot- reloads individual settings within about one second. The process never reads business secrets from the environment in normal operation.

Health. GET /healthz returns 200 unconditionally — it is liveness, not readiness. GET /ready returns 503 until configuration, Redis, and both upstreams (a JMAP session GET and Telegram getMe, 3s each) are reachable. Probe /ready, not /healthz, when deciding whether to route traffic — but /ready is the heavier probe (~3s worst case), so a monitor that must stay cheap should watch the gateway's aggregated /healthz.

Remote debug (opt-in, off by default). /debug/* is an optional remote-debug surface: live JMAP and Telegram probes, the current business config, and a single Telegram send. It sits behind a two-factor gate — the process must be started with --debug and DEBUG_TOKEN must be set; miss either and the routes do not exist at all (requests fall through to a generic 404). Nothing under /debug/* is on the gateway allowlist, so it is reachable only on the backend origin itself. The only rule for running it is to keep it off: leave --debug out of the start command and leave DEBUG_TOKEN unset. If you do turn it on for a one-off diagnosis, configure the chat allowlist first — with an empty allowlist the test send is not restricted to any chat. See docs/deployment.md §2.1.

4. Security boundary, in one sentence

All state lives in Redis; the process reads only two environment variables and writes nothing to disk.

One corollary matters for setup: the SPA admin credential — and therefore the bootstrap trust root — is CONFIG_ENCRYPTION_KEY itself, the single 32-byte hex value you supply at startup. It is only compared in constant time, and is never echoed, logged, or stored. The Redis ACL password, if you have one, authenticates the Redis connection alone and is not the credential for any HTTP endpoint.

5. Where to read more

Document What it answers
docs/design.md Why the system is shaped this way: data flow, JMAP semantics, Redis streams, AI consent rules
docs/deployment.md How to deploy: Dockerfile, secrets, Redis hosting, webhook/push/reconcile routing, multi-instance load balancing
docs/reference.md Single source of truth for verifiable facts — Redis keys and TTLs, error codes, routes, environment layers, budgets
docs/roadmap.md Gaps, blockers and the next phase
docs/retired.md What was tried and dropped — abandoned routes, unreleased designs, and names that never existed
AGENTS.md Contribution rules, hard boundaries, the stable-ID index for cross-document references

Facts that matter are traceable. If two documents disagree, docs/reference.md wins.