一键导入
gen-email
Send the daily MorningAI digest as HTML email via SMTP to a configured recipient list.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Send the daily MorningAI digest as HTML email via SMTP to a configured recipient list.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Daily-scheduled AI news tracker. Collects updates from 80+ AI entities across 6 sources every 24 hours (default 08:00 UTC+8). Generates scored, deduplicated Markdown reports. Supports unattended cron/scheduled execution with date-stamped idempotent output.
Unified AI News Tracking Specification - covers Product/Model/Benchmark/Funding types with tracking scope, source standards, timeliness checks, scoring criteria, and record format
Generate cover and per-type infographics for MorningAI
Generate platform-specific social media copy and images for content distribution
Generate concise message digest with image for sharing on messaging platforms (WeChat, Telegram, Slack, etc.)
Track AI model leaderboard rankings over time and detect rank/score changes between snapshots
| name | gen-email |
| version | 1.4.0 |
| description | Send the daily MorningAI digest as HTML email via SMTP to a configured recipient list. |
Distribute the daily AI news digest as multipart HTML email to a configured recipient list via SMTP. Reads the same data_{DATE}.json as gen-message (already scored, deduped, cross-source verified) and produces:
email_{DATE}.html (also used as the email body)email_{DATE}.txt (multipart/alternative pairing)email_{DATE}_manifest.json (per-recipient send status)This is the first true automated push channel in the pipeline — gen-message / gen-social only generate files for manual sharing, while gen-email actually delivers to inboxes.
Factual integrity inherited from gen-message: every item must have a source_url. Items with score ≥ 7 must additionally satisfy verified == true. Drop items lacking a credible source.
| Variable | Required | Default | Description |
|---|---|---|---|
EMAIL_ENABLED | — | false | Master switch — when unset/false, the step is silently skipped |
EMAIL_RECIPIENTS | ✓ | — | Comma-separated list, e.g. alice@x.com,bob@y.com |
EMAIL_RECIPIENTS_FILE | — | .claude/recipients.json | Optional JSON file — overrides env list when present |
EMAIL_SMTP_HOST | ✓ | — | SMTP server, e.g. smtp.gmail.com, smtp.qq.com |
EMAIL_SMTP_PORT | — | 587 | Port (587=STARTTLS, 465=SSL, 25=plain) |
EMAIL_SMTP_TLS | — | starttls | starttls / ssl / none |
EMAIL_SMTP_USER | ✓ | — | SMTP auth username |
EMAIL_SMTP_PASSWORD | ✓ | — | SMTP password (Gmail: use App Password, not account password) |
EMAIL_FROM | — | EMAIL_SMTP_USER | Display sender, e.g. MorningAI <noreply@x.com> |
EMAIL_REPLY_TO | — | — | Reply-To header |
EMAIL_SUBJECT_TEMPLATE | — | MorningAI {date} · {n} updates | Subject template, supports {date} {n} {lang} |
EMAIL_LANG | — | follows --lang | Email language: zh / en / ja |
EMAIL_MIN_SCORE | — | 5 | Min importance score to include |
EMAIL_MAX_ITEMS | — | 10 | Max items in the digest |
EMAIL_ATTACH_IMAGE | — | true | Attach message_{DATE}.png if it exists |
EMAIL_RATE_LIMIT_DELAY | — | 1 | Seconds to sleep between recipients (anti-throttling) |
EMAIL_LIST_UNSUBSCRIBE | — | — | Unsubscribe target, e.g. mailto:admin@x.com?subject=Unsubscribe |
EMAIL_DRY_RUN | — | false | If true, generate email_*.html/email_*.txt locally and do not send |
EMAIL_KOL_ENABLED | — | true | Render a separate "KOL Voices" section after the main items. Set false to suppress entirely |
EMAIL_KOL_MIN_SCORE | — | 4 | Min importance for KOL items (independent of EMAIL_MIN_SCORE because KOL voices are scored 4-7 by design) |
EMAIL_KOL_MAX_ITEMS | — | 5 | Max KOL items to include in the section |
EMAIL_KOL_SHOW_EMPTY | — | true | When no KOL items qualify, still render the section header with a "today was quiet" empty-state line. Set false to omit the section on quiet days |
EMAIL_RECIPIENTS_FILE)[
{"email": "alice@example.com", "name": "Alice", "lang": "zh", "min_score": 6, "active": true},
{"email": "bob@example.com"},
{"email": "paused@example.com", "active": false}
]
Per-recipient lang and min_score override the global defaults. Inactive entries are skipped.
Same as gen-message (see skills/gen-message/SKILL.md for full spec):
data_{DATE}.json from CWD.importance >= min_score (per-recipient or global), source_url present, verified == true for score ≥ 7.is_kol_voice == true — they render in the dedicated KOL section, not mixed into the main feed.product ≤ 4, model ≤ 3, benchmark ≤ 2, financing ≤ 2; overflow filled by score.importance desc, cap at EMAIL_MAX_ITEMS.A separate block rendered after the main items, mirroring the ## KOL Voices section in report_{DATE}.md and the dedicated KOL channel in gen-social. Independent commentary deserves its own visual lane — without it, KOL takes either get suppressed by the 7+ verification gate (KOL voices are scored 4-7 by design) or appear interleaved with vendor announcements.
KOL filter rules:
is_kol_voice == true.importance >= EMAIL_KOL_MIN_SCORE (default 4, lower than the main EMAIL_MIN_SCORE).source_url.gen-infographic's KOL handling: KOL voices are commentary, not vendor announcements that need cross-source verification.importance desc, capped at EMAIL_KOL_MAX_ITEMS.When zero KOL items qualify, the section is still rendered with a "today's KOL voices were quiet" line if EMAIL_KOL_SHOW_EMPTY=true (the default), so subscribers see the section is alive even on quiet days. Set EMAIL_KOL_SHOW_EMPTY=false to omit the section entirely on quiet days, or EMAIL_KOL_ENABLED=false to suppress it permanently.
The send_email.py script handles all of the following automatically:
EMAIL_ENABLED != true.EMAIL_SMTP_HOST / EMAIL_SMTP_USER / EMAIL_SMTP_PASSWORD / recipients) — exit with clear error if missing.data_{DATE}.json and apply selection rules.EMAIL_RECIPIENTS_FILE (if exists) or EMAIL_RECIPIENTS.EMAIL_LANG → write email_{DATE}.html and email_{DATE}.txt.EMAIL_DRY_RUN=true, exit after writing previews (no SMTP traffic).EMAIL_RATE_LIMIT_DELAY seconds between sends.email_{DATE}_manifest.json with per-recipient status (sent / failed) and error text.The orchestrator (skills/morning-ai/SKILL.md Step 7) runs:
python3 skills/gen-email/scripts/send_email.py --date {YYYY-MM-DD}
Optional flags:
--date YYYY-MM-DD — override the date (default: today UTC+8)--data PATH — override the data file path (default: data_{DATE}.json in CWD)--lang zh|en|ja — override EMAIL_LANG--dry-run — equivalent to EMAIL_DRY_RUN=trueEMAIL_LIST_UNSUBSCRIBE is set. Major clients (Gmail, Outlook, Apple Mail) display a one-click unsubscribe button.EMAIL_RATE_LIMIT_DELAY) prevents tripping SMTP provider throttles (Gmail caps free accounts at ~100 sends/day, with short bursts blocked).When a recipient asks to unsubscribe, the admin manually removes them from EMAIL_RECIPIENTS or recipients.json. Self-service unsubscribe requires a hosted backend (out of scope for this skill).
See docs/email-setup.md for full details. Quick reference:
| Provider | Host | Port | TLS | Notes |
|---|---|---|---|---|
| Gmail | smtp.gmail.com | 587 | STARTTLS | Requires App Password (turn on 2FA → generate App Password) |
| QQ Mail | smtp.qq.com | 465 | SSL | Use authorization code, not login password |
| Outlook | smtp.office365.com | 587 | STARTTLS | Account password or App Password |
| Alibaba Cloud Enterprise Mail | smtp.qiye.aliyun.com | 465 | SSL | Account password |
{
"date": "2026-04-20",
"subject": "MorningAI 2026-04-20 · 8 updates",
"total": 3,
"succeeded": 2,
"failed": 1,
"results": [
{"email": "alice@example.com", "status": "sent", "message_id": "<...@morning-ai>"},
{"email": "bob@example.com", "status": "sent", "message_id": "<...@morning-ai>"},
{"email": "carol@example.com", "status": "failed", "error": "smtp error: 550 mailbox unavailable"}
]
}
The manifest enables retry logic in future runs (skip already-sent recipients) and makes failures visible without scraping logs.