修正邮件架构:服务端统一使用飞书邮箱,用户邮箱可配置
正确的邮件流程: - 服务端:noreply@pandalla.ai (发送+接收) - 用户端:jiaxicui446@gmail.com (接收通知) - 回复路径:任意邮箱 → noreply@pandalla.ai → 系统处理 优势: - 服务端邮箱统一管理 - 用户可用任意邮箱接收通知 - 支持多邮箱品牌回复命令 - 配置简单,维护容易 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a920a41c90
commit
9d180fca79
|
|
@ -0,0 +1,145 @@
|
|||
# TaskPing 邮件架构说明
|
||||
|
||||
## 📧 正确的邮件流程
|
||||
|
||||
### 1. 服务端配置(TaskPing 系统)
|
||||
|
||||
```
|
||||
发送邮箱: noreply@pandalla.ai (飞书邮箱)
|
||||
接收邮箱: noreply@pandalla.ai (飞书邮箱)
|
||||
```
|
||||
|
||||
**用途**:
|
||||
- 发送通知邮件给用户
|
||||
- 接收用户的回复命令
|
||||
- 处理邮件并注入到 Claude Code CLI
|
||||
|
||||
### 2. 用户端配置
|
||||
|
||||
```
|
||||
通知接收邮箱: jiaxicui446@gmail.com (可配置为任意邮箱)
|
||||
回复目标邮箱: noreply@pandalla.ai
|
||||
```
|
||||
|
||||
**用途**:
|
||||
- 接收 TaskPing 的任务通知
|
||||
- 从任意邮箱回复命令到服务端
|
||||
|
||||
## 🔄 邮件流程图
|
||||
|
||||
```
|
||||
1. 任务通知流程:
|
||||
TaskPing 系统 → noreply@pandalla.ai → jiaxicui446@gmail.com
|
||||
|
||||
2. 命令回复流程:
|
||||
用户邮箱(任意) → noreply@pandalla.ai → TaskPing 系统 → Claude Code CLI
|
||||
```
|
||||
|
||||
## 📱 支持的用户邮箱
|
||||
|
||||
用户可以从以下**任意邮箱**发送回复到 `noreply@pandalla.ai`:
|
||||
|
||||
- ✅ Gmail (`jiaxicui446@gmail.com`)
|
||||
- ✅ QQ邮箱 (`xxx@qq.com`)
|
||||
- ✅ 163邮箱 (`xxx@163.com`)
|
||||
- ✅ Outlook (`xxx@outlook.com`)
|
||||
- ✅ 企业邮箱 (`xxx@company.com`)
|
||||
- ✅ 任何支持SMTP的邮箱
|
||||
|
||||
## 🔧 配置文件
|
||||
|
||||
### .env 配置
|
||||
|
||||
```env
|
||||
# 发件配置(飞书邮箱)
|
||||
SMTP_HOST=smtp.feishu.cn
|
||||
SMTP_USER=noreply@pandalla.ai
|
||||
SMTP_PASS=kKgS3tNReRTL3RQC
|
||||
|
||||
# 收件配置(飞书邮箱)
|
||||
IMAP_HOST=imap.feishu.cn
|
||||
IMAP_USER=noreply@pandalla.ai
|
||||
IMAP_PASS=kKgS3tNReRTL3RQC
|
||||
|
||||
# 用户通知邮箱(可配置)
|
||||
EMAIL_TO=jiaxicui446@gmail.com
|
||||
|
||||
# 白名单(可配置多个用户邮箱)
|
||||
ALLOWED_SENDERS=jiaxicui446@gmail.com
|
||||
```
|
||||
|
||||
## 📝 使用方法
|
||||
|
||||
### 1. 接收通知
|
||||
用户在 `jiaxicui446@gmail.com` 收到类似邮件:
|
||||
```
|
||||
发件人: TaskPing 通知系统 <noreply@pandalla.ai>
|
||||
主题: [TaskPing #ABC123] 任务等待您的指示
|
||||
内容: 任务详情...
|
||||
```
|
||||
|
||||
### 2. 发送命令
|
||||
用户可以:
|
||||
|
||||
**方式1:直接回复**
|
||||
- 直接回复邮件
|
||||
- 输入命令,如:`继续执行`
|
||||
|
||||
**方式2:新邮件**
|
||||
- 从任意邮箱发送到 `noreply@pandalla.ai`
|
||||
- 主题包含:`[TaskPing #ABC123]`
|
||||
- 内容为命令
|
||||
|
||||
### 3. 系统处理
|
||||
1. 飞书邮箱接收用户回复
|
||||
2. TaskPing 解析命令
|
||||
3. 通过 PTY 注入到 Claude Code
|
||||
4. 任务继续执行
|
||||
|
||||
## 🔒 安全特性
|
||||
|
||||
1. **发件人验证**:只有白名单中的邮箱可以发送命令
|
||||
2. **Token验证**:邮件主题必须包含有效的会话Token
|
||||
3. **命令过滤**:自动过滤危险命令
|
||||
4. **会话过期**:Token有时间限制
|
||||
5. **去重处理**:防止重复执行同一命令
|
||||
|
||||
## 🚀 优势
|
||||
|
||||
1. **统一管理**:服务端使用单一邮箱管理
|
||||
2. **用户灵活**:用户可用任意邮箱接收和回复
|
||||
3. **简单配置**:只需配置一个飞书邮箱
|
||||
4. **多用户支持**:可配置多个用户邮箱到白名单
|
||||
5. **跨平台**:支持所有邮件客户端
|
||||
|
||||
## 📊 实际场景
|
||||
|
||||
### 场景1:移动办公
|
||||
```
|
||||
1. 开发者在电脑上运行 Claude Code 构建项目
|
||||
2. 离开电脑,在手机 Gmail 收到构建完成通知
|
||||
3. 直接在手机回复:"继续部署"
|
||||
4. 电脑上的 Claude Code 自动执行部署命令
|
||||
```
|
||||
|
||||
### 场景2:团队协作
|
||||
```
|
||||
1. 团队领导 leader@company.com 收到项目通知
|
||||
2. 从企业邮箱回复:"批准发布"
|
||||
3. 系统自动执行发布流程
|
||||
4. 所有团队成员收到发布完成通知
|
||||
```
|
||||
|
||||
### 场景3:多设备同步
|
||||
```
|
||||
1. 在办公室电脑启动长时间任务
|
||||
2. 回家路上用个人 QQ 邮箱收到通知
|
||||
3. 在家用 163 邮箱发送下一步指令
|
||||
4. 办公室电脑自动执行,第二天查看结果
|
||||
```
|
||||
|
||||
这种架构实现了:
|
||||
- 服务端邮箱统一管理
|
||||
- 用户邮箱灵活配置
|
||||
- 多邮箱品牌支持
|
||||
- 简单可靠的通信机制
|
||||
|
|
@ -18,7 +18,8 @@
|
|||
"relay:test": "node test-email-reply.js",
|
||||
"relay:start": "INJECTION_MODE=pty node src/relay/relay-pty.js",
|
||||
"email:config": "node update-email-config.js",
|
||||
"email:test": "node test-email-send.js"
|
||||
"email:test": "node test-email-send.js",
|
||||
"gmail:setup": "node setup-gmail-app-password.js"
|
||||
},
|
||||
"bin": {
|
||||
"taskping-install": "./install.js",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,224 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Gmail 应用专用密码设置指南
|
||||
* 帮助用户配置 Gmail 的应用专用密码
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const readline = require('readline');
|
||||
|
||||
function displayInstructions() {
|
||||
console.log('╔═══════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ Gmail 应用专用密码配置指南 ║');
|
||||
console.log('╚═══════════════════════════════════════════════════════════════╝\n');
|
||||
|
||||
console.log('📋 配置步骤:\n');
|
||||
|
||||
console.log('1️⃣ 登录 Gmail 账号');
|
||||
console.log(' 🌐 访问: https://myaccount.google.com/');
|
||||
console.log(' 👤 使用您的账号: jiaxicui446@gmail.com\n');
|
||||
|
||||
console.log('2️⃣ 开启两步验证(如果未开启)');
|
||||
console.log(' 🌐 访问: https://myaccount.google.com/security');
|
||||
console.log(' 🔐 找到"两步验证"并开启');
|
||||
console.log(' 📱 可以使用手机短信或认证器应用\n');
|
||||
|
||||
console.log('3️⃣ 生成应用专用密码');
|
||||
console.log(' 🌐 访问: https://myaccount.google.com/apppasswords');
|
||||
console.log(' 📧 选择应用: "邮件"');
|
||||
console.log(' 💻 选择设备: "其他(自定义名称)"');
|
||||
console.log(' ✏️ 输入名称: "TaskPing Email Relay"');
|
||||
console.log(' 🔑 点击"生成"');
|
||||
console.log(' 📋 复制 16 位密码(格式类似:abcd efgh ijkl mnop)\n');
|
||||
|
||||
console.log('4️⃣ 启用 IMAP(如果未启用)');
|
||||
console.log(' 🌐 访问: https://mail.google.com/mail/u/0/#settings/fwdandpop');
|
||||
console.log(' 📩 找到"IMAP 访问"');
|
||||
console.log(' ✅ 选择"启用 IMAP"');
|
||||
console.log(' 💾 保存更改\n');
|
||||
|
||||
console.log('⚠️ 重要提示:');
|
||||
console.log(' • 应用专用密码只显示一次,请妥善保存');
|
||||
console.log(' • 不要使用您的 Google 账号密码');
|
||||
console.log(' • 密码格式是 16 位,包含空格(请保留空格)');
|
||||
console.log(' • 如果忘记密码,需要删除后重新生成\n');
|
||||
}
|
||||
|
||||
async function promptForPassword() {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
console.log('📝 请输入您生成的应用专用密码:');
|
||||
console.log(' (格式: abcd efgh ijkl mnop)');
|
||||
rl.question('密码: ', (password) => {
|
||||
rl.close();
|
||||
resolve(password.trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function validatePassword(password) {
|
||||
// Gmail 应用专用密码格式:16位字符,可能包含空格
|
||||
const cleanPassword = password.replace(/\s/g, '');
|
||||
|
||||
if (cleanPassword.length !== 16) {
|
||||
return {
|
||||
valid: false,
|
||||
message: '密码长度应为16位字符'
|
||||
};
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9]+$/.test(cleanPassword)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: '密码只能包含字母和数字'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
cleanPassword
|
||||
};
|
||||
}
|
||||
|
||||
function updateEnvFile(password) {
|
||||
const envPath = '.env';
|
||||
|
||||
if (!fs.existsSync(envPath)) {
|
||||
console.error('❌ 未找到 .env 文件');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
let content = fs.readFileSync(envPath, 'utf8');
|
||||
|
||||
// 替换 IMAP_PASS 行
|
||||
const updated = content.replace(
|
||||
/IMAP_PASS=.*/,
|
||||
`IMAP_PASS=${password}`
|
||||
);
|
||||
|
||||
fs.writeFileSync(envPath, updated);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 更新 .env 文件失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function testConnection(password) {
|
||||
console.log('\n🔍 测试 Gmail IMAP 连接...');
|
||||
|
||||
// 临时设置环境变量
|
||||
process.env.IMAP_PASS = password;
|
||||
|
||||
try {
|
||||
// 动态导入 ImapFlow
|
||||
const { ImapFlow } = require('imapflow');
|
||||
|
||||
const client = new ImapFlow({
|
||||
host: 'imap.gmail.com',
|
||||
port: 993,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: 'jiaxicui446@gmail.com',
|
||||
pass: password
|
||||
},
|
||||
logger: false
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
console.log('✅ Gmail IMAP 连接成功!');
|
||||
|
||||
const status = await client.status('INBOX', { messages: true, unseen: true });
|
||||
console.log(`📧 收件箱状态: ${status.messages} 封邮件,${status.unseen} 封未读`);
|
||||
|
||||
await client.logout();
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 连接失败:', error.message);
|
||||
|
||||
if (error.code === 'EAUTH') {
|
||||
console.log('\n可能的原因:');
|
||||
console.log('• 应用专用密码错误');
|
||||
console.log('• 两步验证未开启');
|
||||
console.log('• IMAP 未启用');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
displayInstructions();
|
||||
|
||||
console.log('═'.repeat(65));
|
||||
console.log('现在开始配置应用专用密码\n');
|
||||
|
||||
// 检查是否已经完成网页配置
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
const ready = await new Promise((resolve) => {
|
||||
rl.question('已完成上述步骤并获得应用专用密码?(y/n): ', (answer) => {
|
||||
rl.close();
|
||||
resolve(answer.toLowerCase() === 'y');
|
||||
});
|
||||
});
|
||||
|
||||
if (!ready) {
|
||||
console.log('\n请先完成上述配置步骤,然后重新运行此脚本');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取密码
|
||||
const password = await promptForPassword();
|
||||
|
||||
if (!password) {
|
||||
console.log('❌ 未输入密码');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证密码格式
|
||||
const validation = validatePassword(password);
|
||||
if (!validation.valid) {
|
||||
console.log(`❌ 密码格式错误: ${validation.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanPassword = validation.cleanPassword;
|
||||
console.log('✅ 密码格式正确');
|
||||
|
||||
// 测试连接
|
||||
const connected = await testConnection(cleanPassword);
|
||||
|
||||
if (!connected) {
|
||||
console.log('\n请检查配置后重试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 .env 文件
|
||||
const updated = updateEnvFile(cleanPassword);
|
||||
|
||||
if (updated) {
|
||||
console.log('\n✅ 配置已保存到 .env 文件');
|
||||
console.log('\n🚀 下一步操作:');
|
||||
console.log(' 1. 运行: npm run email:config');
|
||||
console.log(' 2. 运行: npm run email:test');
|
||||
console.log(' 3. 运行: npm run relay:pty');
|
||||
console.log('\n🎉 Gmail 应用专用密码配置完成!');
|
||||
} else {
|
||||
console.log('\n❌ 保存配置失败,请手动编辑 .env 文件');
|
||||
console.log(` IMAP_PASS=${cleanPassword}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行
|
||||
main().catch(console.error);
|
||||
|
|
@ -12,5 +12,12 @@
|
|||
"expiresAt": 1753559689,
|
||||
"cwd": "/Users/jessytsui/dev/TaskPing",
|
||||
"description": "测试会话"
|
||||
},
|
||||
"TESTMDKMI9XE": {
|
||||
"type": "pty",
|
||||
"createdAt": 1753556908,
|
||||
"expiresAt": 1753560508,
|
||||
"cwd": "/Users/jessytsui/dev/TaskPing",
|
||||
"description": "测试会话"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "c279add4-8ae5-4c7d-958a-2453d2ac6f21",
|
||||
"created": "2025-07-26T18:59:12.862Z",
|
||||
"expires": "2025-07-27T18:59:12.862Z",
|
||||
"notification": {
|
||||
"type": "completed",
|
||||
"project": "TaskPing",
|
||||
"message": "[TaskPing] 任务已完成,Claude正在等待下一步指令"
|
||||
},
|
||||
"status": "waiting",
|
||||
"commandCount": 0,
|
||||
"maxCommands": 10
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "fb9c0090-dad7-4482-9885-e0b84daaa4e4",
|
||||
"created": "2025-07-26T19:05:11.062Z",
|
||||
"expires": "2025-07-27T19:05:11.062Z",
|
||||
"notification": {
|
||||
"type": "completed",
|
||||
"project": "TaskPing",
|
||||
"message": "[TaskPing] 任务已完成,Claude正在等待下一步指令"
|
||||
},
|
||||
"status": "waiting",
|
||||
"commandCount": 0,
|
||||
"maxCommands": 10
|
||||
}
|
||||
|
|
@ -75,11 +75,11 @@ async function testEmailSend() {
|
|||
</div>
|
||||
|
||||
<div style="background: #e8f4f8; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||
<p><strong>📝 测试指令:</strong></p>
|
||||
<p>请回复以下任意命令来测试:</p>
|
||||
<p><strong>📝 如何回复测试指令:</strong></p>
|
||||
<p><strong>方式1(推荐)</strong>:直接回复此邮件,内容输入:</p>
|
||||
<ul>
|
||||
<li>直接回复: <code>echo "Hello from email"</code></li>
|
||||
<li>使用CMD前缀: <code>CMD: pwd</code></li>
|
||||
<li><code>echo "Hello from email"</code></li>
|
||||
<li><code>CMD: pwd</code></li>
|
||||
<li>使用代码块:
|
||||
<pre style="background: white; padding: 10px; border-radius: 3px;">
|
||||
\`\`\`
|
||||
|
|
@ -88,6 +88,19 @@ ls -la
|
|||
</pre>
|
||||
</li>
|
||||
</ul>
|
||||
<p><strong>方式2</strong>:从任何邮箱发送邮件到 <strong style="color: #007bff;">noreply@pandalla.ai</strong></p>
|
||||
<p>主题必须包含:<code>[TaskPing #${testToken}]</code></p>
|
||||
</div>
|
||||
|
||||
<div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #ffc107;">
|
||||
<p><strong>💡 多邮箱支持:</strong></p>
|
||||
<p>您可以从以下任意邮箱发送回复到 <strong>noreply@pandalla.ai</strong>:</p>
|
||||
<ul>
|
||||
<li>Gmail、QQ邮箱、163邮箱</li>
|
||||
<li>Outlook、企业邮箱</li>
|
||||
<li>任何支持SMTP的邮箱</li>
|
||||
</ul>
|
||||
<p><strong>回复路径</strong>:您的邮箱 → noreply@pandalla.ai → TaskPing系统处理</p>
|
||||
</div>
|
||||
|
||||
<hr style="border: none; border-top: 1px solid #ddd; margin: 30px 0;">
|
||||
|
|
@ -95,7 +108,9 @@ ls -la
|
|||
<p style="color: #666; font-size: 12px;">
|
||||
会话ID: ${testToken}<br>
|
||||
发送自: ${process.env.SMTP_USER}<br>
|
||||
发送到: ${process.env.EMAIL_TO || 'jiaxicui446@gmail.com'}
|
||||
通知邮箱: ${process.env.EMAIL_TO}<br>
|
||||
回复邮箱: ${process.env.SMTP_USER}<br>
|
||||
系统支持任意邮箱回复
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
|
|
|
|||
Loading…
Reference in New Issue