
Qq Email
- 8 installs
- 33 repo stars
- Updated April 26, 2026
- bighardperson/computer-science-skills-collection
Qq-email is a skill that sends and receives QQ Mail via IMAP and SMTP using Node.js scripts, reading credentials from environment variables.
About
Qq-email is an email skill for QQ Mail that receives mail over IMAP and sends over SMTP using Node.js scripts. Credentials come only from the QQ_EMAIL_ACCOUNT and QQ_EMAIL_AUTH_CODE environment variables, never hardcoded. A developer uses it to send, receive, and read QQ email bodies by UID.
- Send and receive QQ Mail via IMAP and SMTP Node.js scripts
- Credentials read only from env vars, never hardcoded
- Fetch full message body by UID; filter by recent N or days
Qq Email by the numbers
- 8 all-time installs (skills.sh)
- Ranked #1,522 of 2,719 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
qq-email capabilities & compatibility
Free; needs a QQ mailbox auth code in env vars.
- Capabilities
- Use cases
- Pricing
- Free
What qq-email says it does
QQ邮箱 IMAP receive and SMTP send via Node.js scripts; credentials read from env vars QQ_EMAIL_ACCOUNT and QQ_EMAIL_AUTH_CODE.
**IMAP**:`imap.qq.com`,端口 993(SSL)
npx skills add https://github.com/bighardperson/computer-science-skills-collection --skill qq-emailAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 33 |
| Last updated | April 26, 2026 |
| Repository | bighardperson/computer-science-skills-collection ↗ |
What it does
Send and receive QQ Mail via IMAP/SMTP Node.js scripts with env-var credentials.
Who is it for?
Sending and receiving QQ Mail programmatically with env-var credentials
Skip if: Non-QQ mail providers such as Gmail or Outlook
When should I use this skill?
A user asks to send, receive, check, or configure QQ email
What you get
QQ emails sent, received with UIDs, and full bodies retrieved by UID.
- sent QQ emails
- received email list with UIDs
- full message bodies
By the numbers
- 3 scripts: send.js, receive.js, get-body.js
- IMAP port 993, SMTP port 465
Files
何时使用
用户要使用 QQ 邮箱 发邮件、收邮件、查邮件、代发邮件或配置 QQ 邮箱时使用本 skill。
QQ 邮箱收发
面向 QQ 邮箱:通过 IMAP 收取邮件、SMTP 发送邮件。账号与授权码仅从环境变量读取,不在代码或配置中硬编码。
凭证(环境变量)
| 变量 | 说明 |
|---|---|
| QQ_EMAIL_ACCOUNT | QQ 邮箱账号(完整地址,如 xxx@qq.com) |
| QQ_EMAIL_AUTH_CODE | QQ 邮箱授权码(在 QQ 邮箱「设置 → 账户与安全 → 安全设置」中开启 IMAP/SMTP 后生成,非 QQ 登录密码;勿提交到仓库) |
脚本会校验,缺失时报错并退出;请勿在终端用 echo 等方式检查,以免泄露授权码。
QQ 邮箱服务器
- IMAP:
imap.qq.com,端口 993(SSL) - SMTP:
smtp.qq.com,端口 465(SSL)
脚本
| 脚本 | 作用 |
|---|---|
scripts/send.js | 从环境变量读凭证,用 nodemailer 连接 QQ 邮箱 SMTP 发信;支持收件人、主题、正文(CLI 参数)。 |
scripts/receive.js | 从环境变量读凭证,用 imap + mailparser 连接 QQ 邮箱 IMAP 收信;支持「最近 N 条」或「最近 N 天」,输出主题、发件人、日期、UID、正文摘要。 |
scripts/get-body.js | 按 UID 获取指定邮件的完整正文(纯文本,无摘要截断)。必须传入 --uid(值为收信列表中的 UID)。 |
发信流程
在 skill 根目录下执行(需已 npm install):
node scripts/send.js <收件人> <主题> <正文>正文若含空格,请用引号包裹;或只传收件人和主题,正文从 stdin 读入(见脚本 --stdin)。
示例:
node scripts/send.js "recipient@example.com" "测试主题" "邮件正文内容"收信流程
# 收取最近 10 条(默认)
node scripts/receive.js
# 收取最近 N 条
node scripts/receive.js --limit 20
# 收取最近 N 天的邮件(如 7、30、90)
node scripts/receive.js --days 7输出:每封邮件的主题、发件人、日期、UID(收件箱内唯一标识,用于按 UID 取正文)、正文摘要(前约 200 字),便于查看。
获取邮件正文
需要某封邮件的完整正文时,使用 get-body.js,传入收信列表中该邮件的 UID:
node scripts/get-body.js --uid 12345未传 --uid 时会提示并退出。UID 与收件箱绑定,邮件移动或删除后可能失效。
- 输出:完整正文输出到 stdout(纯文本;若原邮件仅有 HTML,会做简单去标签后输出)。可重定向到文件或管道给其它命令。
- 环境变量:与收信相同,需
QQ_EMAIL_ACCOUNT、QQ_EMAIL_AUTH_CODE。
可选能力(与「收取选项」对应)
- 收取时间范围:通过
--days 7/--days 30/--days 90使用 IMAP SINCE 条件。 - 收取「我的文件夹」:当前脚本默认 INBOX;若需自定义文件夹,可扩展脚本中的
openBox(如openBox('我的文件夹', ...))。
安全提醒
- QQ 邮箱授权码需在「设置 → 账户」中开启 IMAP/SMTP 服务后生成,与 QQ 登录密码不同,不要混淆。
- 不要将
QQ_EMAIL_ACCOUNT、QQ_EMAIL_AUTH_CODE的真实值写入代码或提交到仓库;仅通过环境变量或本地.env配置。
{
"name": "qq-email",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "qq-email",
"version": "1.0.0",
"dependencies": {
"imap": "^0.8.19",
"mailparser": "^3.6.5",
"nodemailer": "^6.9.7"
}
},
"node_modules/@selderee/plugin-htmlparser2": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
"integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"selderee": "^0.11.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/@zone-eu/mailsplit": {
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz",
"integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==",
"license": "(MIT OR EUPL-1.1+)",
"dependencies": {
"libbase64": "1.3.0",
"libmime": "5.3.7",
"libqp": "2.1.1"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/encoding-japanese": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz",
"integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==",
"license": "MIT",
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"license": "MIT",
"bin": {
"he": "bin/he"
}
},
"node_modules/html-to-text": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
"integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
"license": "MIT",
"dependencies": {
"@selderee/plugin-htmlparser2": "^0.11.0",
"deepmerge": "^4.3.1",
"dom-serializer": "^2.0.0",
"htmlparser2": "^8.0.2",
"selderee": "^0.11.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/imap": {
"version": "0.8.19",
"resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz",
"integrity": "sha512-z5DxEA1uRnZG73UcPA4ES5NSCGnPuuouUx43OPX7KZx1yzq3N8/vx2mtXEShT5inxB3pRgnfG1hijfu7XN2YMw==",
"dependencies": {
"readable-stream": "1.1.x",
"utf7": ">=1.0.2"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"license": "MIT"
},
"node_modules/leac": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
"integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
"license": "MIT",
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/libbase64": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz",
"integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==",
"license": "MIT"
},
"node_modules/libmime": {
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz",
"integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==",
"license": "MIT",
"dependencies": {
"encoding-japanese": "2.2.0",
"iconv-lite": "0.6.3",
"libbase64": "1.3.0",
"libqp": "2.1.1"
}
},
"node_modules/libmime/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/libqp": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz",
"integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==",
"license": "MIT"
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/mailparser": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.3.tgz",
"integrity": "sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ==",
"license": "MIT",
"dependencies": {
"@zone-eu/mailsplit": "5.4.8",
"encoding-japanese": "2.2.0",
"he": "1.2.0",
"html-to-text": "9.0.5",
"iconv-lite": "0.7.2",
"libmime": "5.3.7",
"linkify-it": "5.0.0",
"nodemailer": "7.0.13",
"punycode.js": "2.3.1",
"tlds": "1.261.0"
}
},
"node_modules/mailparser/node_modules/nodemailer": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz",
"integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nodemailer": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz",
"integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/parseley": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
"integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
"license": "MIT",
"dependencies": {
"leac": "^0.6.0",
"peberminta": "^0.9.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/peberminta": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
"integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
"license": "MIT",
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/selderee": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
"integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
"license": "MIT",
"dependencies": {
"parseley": "^0.12.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/semver": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==",
"license": "ISC",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
"license": "MIT"
},
"node_modules/tlds": {
"version": "1.261.0",
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz",
"integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==",
"license": "MIT",
"bin": {
"tlds": "bin.js"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/utf7": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz",
"integrity": "sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==",
"dependencies": {
"semver": "~5.3.0"
}
}
}
}
{
"name": "qq-email",
"version": "1.0.0",
"type": "module",
"private": true,
"dependencies": {
"imap": "^0.8.19",
"mailparser": "^3.6.5",
"nodemailer": "^6.9.7"
}
}import Imap from 'imap';
import { simpleParser } from 'mailparser';
const account = process.env.QQ_EMAIL_ACCOUNT;
const authCode = process.env.QQ_EMAIL_AUTH_CODE;
if (!account || !authCode) {
console.error('请设置环境变量 QQ_EMAIL_ACCOUNT 和 QQ_EMAIL_AUTH_CODE');
process.exit(1);
}
const imapConfig = {
user: account,
password: authCode,
host: 'imap.qq.com',
port: 993,
tls: true,
tlsOptions: { rejectUnauthorized: false },
};
function parseArgs() {
const args = process.argv.slice(2);
let uid = null;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--uid' && args[i + 1]) {
const n = parseInt(args[i + 1].trim(), 10);
if (!Number.isNaN(n) && n > 0) uid = n;
i++;
}
}
return { uid };
}
function openInbox(imap) {
return new Promise((resolve, reject) => {
imap.openBox('INBOX', false, (err, box) => {
if (err) reject(err);
else resolve(box);
});
});
}
/** 从 HTML 中粗略提取纯文本 */
function htmlToText(html) {
if (!html || typeof html !== 'string') return '';
return html
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.replace(/ /g, ' ')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/&/g, '&')
.replace(/"/g, '"')
.trim();
}
function getBodyText(parsed) {
const text = parsed.text?.trim();
if (text) return text;
const html = parsed.html;
if (html) return htmlToText(html);
return '';
}
/** 按 UID 在 INBOX 中取回一封邮件的解析结果 */
async function fetchByUid(uid) {
const imap = new Imap(imapConfig);
return new Promise((resolve, reject) => {
let parsed = null;
let parseDone = null;
const parsePromise = new Promise((r) => { parseDone = r; });
imap.once('ready', () => {
openInbox(imap)
.then(() => {
const fetch = imap.fetch(uid, { bodies: '' });
fetch.on('message', (msg) => {
msg.on('body', (stream) => {
simpleParser(stream, (parseErr, result) => {
if (!parseErr) parsed = result;
parseDone();
});
});
});
fetch.once('error', (e) => {
imap.end();
reject(e);
});
fetch.once('end', () => {
parsePromise.then(() => imap.end());
});
})
.catch((e) => {
imap.end();
reject(e);
});
});
imap.once('error', reject);
imap.once('end', () => resolve(parsed));
imap.connect();
});
}
async function main() {
const { uid } = parseArgs();
if (!uid) {
console.error('请提供 --uid,值为收信列表中的 UID');
process.exit(1);
}
try {
const email = await fetchByUid(uid);
if (!email) {
console.error('未找到该 UID 的邮件');
process.exit(1);
}
const body = getBodyText(email);
if (!body) {
console.error('该邮件无正文内容');
process.exit(1);
}
process.stdout.write(body);
} catch (err) {
console.error('获取正文失败:', err.message);
process.exit(1);
}
}
main();
import Imap from 'imap';
import { simpleParser } from 'mailparser';
const account = process.env.QQ_EMAIL_ACCOUNT;
const authCode = process.env.QQ_EMAIL_AUTH_CODE;
if (!account || !authCode) {
console.error('请设置环境变量 QQ_EMAIL_ACCOUNT 和 QQ_EMAIL_AUTH_CODE');
process.exit(1);
}
const imapConfig = {
user: account,
password: authCode,
host: 'imap.qq.com',
port: 993,
tls: true,
tlsOptions: { rejectUnauthorized: false },
};
function parseArgs() {
const args = process.argv.slice(2);
let limit = 10;
let days = null;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--limit' && args[i + 1]) {
limit = Math.max(1, parseInt(args[i + 1], 10) || 10);
i++;
} else if (args[i] === '--days' && args[i + 1]) {
days = Math.max(1, parseInt(args[i + 1], 10) || 7);
i++;
}
}
return { limit, days };
}
function openInbox(imap) {
return new Promise((resolve, reject) => {
imap.openBox('INBOX', false, (err, box) => {
if (err) reject(err);
else resolve(box);
});
});
}
function getPreview(text, maxLen = 200) {
if (!text || typeof text !== 'string') return '';
const oneLine = text.replace(/\s+/g, ' ').trim();
return oneLine.length <= maxLen ? oneLine : oneLine.slice(0, maxLen) + '…';
}
async function fetchEmails(limit, sinceDate) {
const imap = new Imap(imapConfig);
return new Promise((resolve, reject) => {
const emails = [];
imap.once('ready', () => {
openInbox(imap)
.then(() => {
const searchCriteria = sinceDate ? [['SINCE', sinceDate]] : ['ALL'];
imap.search(searchCriteria, (err, uids) => {
if (err) {
imap.end();
return reject(err);
}
if (uids.length === 0) {
imap.end();
return resolve(emails);
}
const slice = uids.slice(-limit);
const fetch = imap.fetch(slice, { bodies: '' });
const parsePromises = [];
fetch.on('message', (msg) => {
let resolveP;
parsePromises.push(new Promise((r) => { resolveP = r; }));
const state = { parsed: null, uid: undefined, pushed: false };
function maybePush() {
if (state.parsed != null && state.uid !== undefined && !state.pushed) {
state.pushed = true;
emails.push({ parsed: state.parsed, uid: state.uid });
resolveP();
}
}
msg.once('attributes', (attrs) => {
state.uid = attrs && attrs.uid;
maybePush();
});
msg.on('body', (stream) => {
simpleParser(stream, (parseErr, parsed) => {
if (!parseErr) state.parsed = parsed;
maybePush();
});
});
msg.once('end', () => {
if (!state.pushed && state.parsed != null) {
state.pushed = true;
emails.push({ parsed: state.parsed, uid: state.uid });
resolveP();
}
});
});
fetch.once('error', (e) => {
imap.end();
reject(e);
});
fetch.once('end', () => {
Promise.all(parsePromises).then(() => imap.end());
});
});
})
.catch((e) => {
imap.end();
reject(e);
});
});
imap.once('error', reject);
imap.once('end', () => resolve(emails));
imap.connect();
});
}
async function main() {
const { limit, days } = parseArgs();
let sinceDate = null;
if (days) {
sinceDate = new Date();
sinceDate.setDate(sinceDate.getDate() - days);
}
try {
const emails = await fetchEmails(limit, sinceDate);
if (emails.length === 0) {
console.log('暂无邮件');
return;
}
// 按日期倒序,1 = 最新一封
emails.sort((a, b) => {
const da = a.parsed.date ? new Date(a.parsed.date).getTime() : 0;
const db = b.parsed.date ? new Date(b.parsed.date).getTime() : 0;
return db - da;
});
emails.forEach((item, i) => {
const e = item.parsed;
const from = e.from?.text || e.from?.value?.[0]?.address || '';
const date = e.date ? new Date(e.date).toLocaleString() : '';
const preview = getPreview(e.text || e.html);
console.log(`--- ${i + 1} ---`);
console.log('主题:', e.subject || '(无主题)');
console.log('发件人:', from);
console.log('日期:', date);
console.log('UID:', item.uid ?? '(无)');
console.log('摘要:', preview);
console.log('');
});
} catch (err) {
console.error('收信失败:', err.message);
process.exit(1);
}
}
main();
import nodemailer from 'nodemailer';
const account = process.env.QQ_EMAIL_ACCOUNT;
const authCode = process.env.QQ_EMAIL_AUTH_CODE;
if (!account || !authCode) {
console.error('请设置环境变量 QQ_EMAIL_ACCOUNT 和 QQ_EMAIL_AUTH_CODE');
process.exit(1);
}
const transporter = nodemailer.createTransport({
host: 'smtp.qq.com',
port: 465,
secure: true,
auth: {
user: account,
pass: authCode,
},
});
async function readStdin() {
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
return Buffer.concat(chunks).toString('utf8').trim();
}
async function main() {
const args = process.argv.slice(2);
const useStdin = args.includes('--stdin');
const filtered = args.filter((a) => a !== '--stdin');
let to, subject, body;
if (useStdin) {
if (filtered.length < 2) {
console.error('用法: node scripts/send.js <收件人> <主题> --stdin');
process.exit(1);
}
[to, subject] = filtered;
body = await readStdin();
} else {
if (filtered.length < 3) {
console.error('用法: node scripts/send.js <收件人> <主题> <正文>');
process.exit(1);
}
[to, subject, body] = filtered;
}
try {
const info = await transporter.sendMail({
from: account,
to,
subject,
text: body,
});
console.log('已发送:', info.messageId);
} catch (err) {
console.error('发信失败:', err.message);
process.exit(1);
}
}
main();
Related skills
FAQ
How does qq-email authenticate?
From env vars QQ_EMAIL_ACCOUNT and QQ_EMAIL_AUTH_CODE, using the QQ mailbox auth code, not the login password.
How do you read a full message body?
Run get-body.js with the message UID from the receive list to get the complete plain-text body.