Merge pull request #2 from kevinsslin/fix/email-from-env-variable
Remove hardcoded values and implement environment-based configuration
This commit is contained in:
parent
5ddda6217e
commit
e5de5a7932
67
.env.example
67
.env.example
|
|
@ -1,13 +1,14 @@
|
||||||
# Claude Code Remote Email Configuration
|
# Claude Code Remote Email Configuration Example
|
||||||
|
# Copy this file to .env and configure with your actual values
|
||||||
|
|
||||||
# ===== SMTP 发送邮件配置 =====
|
# ===== SMTP 发送邮件配置 =====
|
||||||
SMTP_HOST=smtp.gmail.com
|
SMTP_HOST=smtp.gmail.com
|
||||||
SMTP_PORT=587
|
SMTP_PORT=465
|
||||||
SMTP_SECURE=false
|
SMTP_SECURE=true
|
||||||
SMTP_USER=your-email@gmail.com
|
SMTP_USER=your-email@gmail.com
|
||||||
SMTP_PASS=your-app-password
|
SMTP_PASS=your-app-password
|
||||||
|
|
||||||
# 发件人信息
|
# 发件人信息 (可选,默认使用 SMTP_USER)
|
||||||
EMAIL_FROM=your-email@gmail.com
|
EMAIL_FROM=your-email@gmail.com
|
||||||
EMAIL_FROM_NAME=Claude Code Remote 通知系统
|
EMAIL_FROM_NAME=Claude Code Remote 通知系统
|
||||||
|
|
||||||
|
|
@ -20,19 +21,19 @@ IMAP_PASS=your-app-password
|
||||||
|
|
||||||
# ===== 邮件路由配置 =====
|
# ===== 邮件路由配置 =====
|
||||||
# 接收通知的邮箱地址
|
# 接收通知的邮箱地址
|
||||||
EMAIL_TO=your-notification-email@gmail.com
|
EMAIL_TO=your-email@gmail.com
|
||||||
|
|
||||||
# 允许发送命令的邮箱地址(安全白名单)
|
# 允许发送命令的邮箱地址(安全白名单)
|
||||||
ALLOWED_SENDERS=your-notification-email@gmail.com
|
ALLOWED_SENDERS=your-email@gmail.com
|
||||||
|
|
||||||
# ===== 系统配置 =====
|
# ===== 系统配置 =====
|
||||||
# 会话映射文件路径 (请替换为你的实际路径)
|
# 会话映射文件路径
|
||||||
SESSION_MAP_PATH=/Users/your-username/path/to/Claude-Code-Remote/src/data/session-map.json
|
SESSION_MAP_PATH=/path/to/your/project/src/data/session-map.json
|
||||||
|
|
||||||
# 运行模式:pty 或 tmux
|
# 运行模式:pty 或 tmux
|
||||||
INJECTION_MODE=pty
|
INJECTION_MODE=pty
|
||||||
|
|
||||||
# Claude CLI 路径(默认使用系统PATH中的claude)
|
# Claude CLI 路径(可选,默认使用系统PATH中的claude)
|
||||||
CLAUDE_CLI_PATH=claude
|
CLAUDE_CLI_PATH=claude
|
||||||
|
|
||||||
# 日志级别:debug, info, warn, error
|
# 日志级别:debug, info, warn, error
|
||||||
|
|
@ -41,9 +42,55 @@ LOG_LEVEL=info
|
||||||
# 是否记录PTY输出(调试用)
|
# 是否记录PTY输出(调试用)
|
||||||
PTY_OUTPUT_LOG=false
|
PTY_OUTPUT_LOG=false
|
||||||
|
|
||||||
|
# ===== 超时配置 =====
|
||||||
|
# 命令执行超时时间(毫秒)
|
||||||
|
COMMAND_TIMEOUT=10000
|
||||||
|
|
||||||
|
# SMTP 连接超时时间(毫秒)
|
||||||
|
SMTP_TIMEOUT=10000
|
||||||
|
|
||||||
|
# 通知超时时间(毫秒)
|
||||||
|
NOTIFICATION_TIMEOUT=3000
|
||||||
|
|
||||||
|
# 通知显示时间(毫秒)
|
||||||
|
NOTIFICATION_DISPLAY_TIME=10000
|
||||||
|
|
||||||
# ===== 邮件模板配置 =====
|
# ===== 邮件模板配置 =====
|
||||||
# 邮件检查间隔(秒)
|
# 邮件检查间隔(秒)
|
||||||
CHECK_INTERVAL=30
|
CHECK_INTERVAL=20
|
||||||
|
|
||||||
# 会话超时时间(小时)
|
# 会话超时时间(小时)
|
||||||
SESSION_TIMEOUT=24
|
SESSION_TIMEOUT=24
|
||||||
|
|
||||||
|
# ===== 测试配置(可选)=====
|
||||||
|
# 测试邮件使用的固定令牌(可选,默认动态生成)
|
||||||
|
TEST_TOKEN=
|
||||||
|
|
||||||
|
# Gmail 应用密码(用于测试脚本,可选)
|
||||||
|
GMAIL_APP_PASSWORD=
|
||||||
|
|
||||||
|
# ===== Gmail 配置说明 =====
|
||||||
|
# 1. 启用两步验证: https://myaccount.google.com/security
|
||||||
|
# 2. 生成应用密码: https://myaccount.google.com/apppasswords
|
||||||
|
# 3. 将生成的16位密码填入 SMTP_PASS 和 IMAP_PASS
|
||||||
|
# 4. 确保 SMTP_PORT=465 和 SMTP_SECURE=true (推荐SSL连接)
|
||||||
|
|
||||||
|
# ===== 其他邮件服务商配置示例 =====
|
||||||
|
# QQ邮箱:
|
||||||
|
# SMTP_HOST=smtp.qq.com
|
||||||
|
# SMTP_PORT=587 或 465
|
||||||
|
# IMAP_HOST=imap.qq.com
|
||||||
|
# IMAP_PORT=993
|
||||||
|
|
||||||
|
# 163邮箱:
|
||||||
|
# SMTP_HOST=smtp.163.com
|
||||||
|
# SMTP_PORT=587 或 465
|
||||||
|
# IMAP_HOST=imap.163.com
|
||||||
|
# IMAP_PORT=993
|
||||||
|
|
||||||
|
# Outlook:
|
||||||
|
# SMTP_HOST=smtp.live.com
|
||||||
|
# SMTP_PORT=587
|
||||||
|
# IMAP_HOST=imap-mail.outlook.com
|
||||||
|
# IMAP_PORT=993
|
||||||
|
EOF < /dev/null
|
||||||
|
|
@ -50,3 +50,4 @@ tmp/
|
||||||
temp/src/data/sessions/
|
temp/src/data/sessions/
|
||||||
src/data/processed-messages.json
|
src/data/processed-messages.json
|
||||||
src/data/session-map.json
|
src/data/session-map.json
|
||||||
|
src/data/sessions/*.json
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,8 @@ class RemoteControlSetup {
|
||||||
}
|
}
|
||||||
// If clauderun fails, try using full path command
|
// If clauderun fails, try using full path command
|
||||||
console.log('🔄 Trying full path command...');
|
console.log('🔄 Trying full path command...');
|
||||||
const fallbackCommand = `tmux new-session -d -s ${this.sessionName} -c "${workingDir}" /Users/jessytsui/.nvm/versions/node/v18.17.0/bin/claude --dangerously-skip-permissions`;
|
const claudePath = process.env.CLAUDE_CLI_PATH || 'claude';
|
||||||
|
const fallbackCommand = `tmux new-session -d -s ${this.sessionName} -c "${workingDir}" ${claudePath} --dangerously-skip-permissions`;
|
||||||
exec(fallbackCommand, (fallbackError) => {
|
exec(fallbackCommand, (fallbackError) => {
|
||||||
if (fallbackError) {
|
if (fallbackError) {
|
||||||
console.log(`❌ Full path command also failed: ${fallbackError.message}`);
|
console.log(`❌ Full path command also failed: ${fallbackError.message}`);
|
||||||
|
|
@ -234,7 +235,7 @@ class RemoteControlSetup {
|
||||||
|
|
||||||
console.log('📱 Email testing:');
|
console.log('📱 Email testing:');
|
||||||
console.log(' Token will include session information, automatically routing to correct tmux session');
|
console.log(' Token will include session information, automatically routing to correct tmux session');
|
||||||
console.log(' Recipient email: jiaxicui446@gmail.com');
|
console.log(` Recipient email: ${process.env.EMAIL_TO}`);
|
||||||
console.log(' Reply with command: echo "Remote control test"\n');
|
console.log(' Reply with command: echo "Remote control test"\n');
|
||||||
|
|
||||||
console.log('🚨 Important reminders:');
|
console.log('🚨 Important reminders:');
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
* Main entry point for the CLI tool
|
* Main entry point for the CLI tool
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
const Logger = require('./src/core/logger');
|
const Logger = require('./src/core/logger');
|
||||||
const Notifier = require('./src/core/notifier');
|
const Notifier = require('./src/core/notifier');
|
||||||
const ConfigManager = require('./src/core/config');
|
const ConfigManager = require('./src/core/config');
|
||||||
|
|
|
||||||
|
|
@ -6,32 +6,7 @@
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "email",
|
"type": "email",
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"config": {
|
|
||||||
"smtp": {
|
|
||||||
"host": "smtp.feishu.cn",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true,
|
|
||||||
"auth": {
|
|
||||||
"user": "noreply@pandalla.ai",
|
|
||||||
"pass": "kKgS3tNReRTL3RQC"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"imap": {
|
|
||||||
"host": "imap.feishu.cn",
|
|
||||||
"port": 993,
|
|
||||||
"secure": true,
|
|
||||||
"auth": {
|
|
||||||
"user": "noreply@pandalla.ai",
|
|
||||||
"pass": "kKgS3tNReRTL3RQC"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"from": "Claude-Code-Remote Notification System <noreply@pandalla.ai>",
|
|
||||||
"to": "jiaxicui446@gmail.com",
|
|
||||||
"template": {
|
|
||||||
"checkInterval": 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"discord": {
|
"discord": {
|
||||||
"type": "chat",
|
"type": "chat",
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,45 @@ require('dotenv').config();
|
||||||
async function sendTestReply() {
|
async function sendTestReply() {
|
||||||
console.log('📧 Sending test email reply...\n');
|
console.log('📧 Sending test email reply...\n');
|
||||||
|
|
||||||
// Create test SMTP transporter (using Gmail)
|
// Create test SMTP transporter (using environment variables)
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
service: 'gmail',
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
|
port: parseInt(process.env.SMTP_PORT) || 587,
|
||||||
|
secure: process.env.SMTP_SECURE === 'true',
|
||||||
auth: {
|
auth: {
|
||||||
user: 'jiaxicui446@gmail.com',
|
user: process.env.SMTP_USER,
|
||||||
pass: process.env.GMAIL_APP_PASSWORD || 'your-app-password'
|
pass: process.env.SMTP_PASS
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use latest token
|
// Generate or use test token from environment
|
||||||
const testToken = 'V5UPZ1UE'; // Latest token from session-map.json
|
let testToken = process.env.TEST_TOKEN;
|
||||||
|
|
||||||
|
if (!testToken) {
|
||||||
|
// Try to read latest token from session map
|
||||||
|
try {
|
||||||
|
const sessionMapPath = process.env.SESSION_MAP_PATH || './src/data/session-map.json';
|
||||||
|
if (require('fs').existsSync(sessionMapPath)) {
|
||||||
|
const sessionMap = JSON.parse(require('fs').readFileSync(sessionMapPath, 'utf8'));
|
||||||
|
const tokens = Object.keys(sessionMap);
|
||||||
|
testToken = tokens[tokens.length - 1]; // Use latest token
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Could not read session map, using generated token');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: generate a test token
|
||||||
|
if (!testToken) {
|
||||||
|
testToken = Math.random().toString(36).substr(2, 8).toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: 'jiaxicui446@gmail.com',
|
from: process.env.SMTP_USER,
|
||||||
to: 'noreply@pandalla.ai',
|
to: process.env.SMTP_USER, // Self-send for testing
|
||||||
subject: `Re: [Claude-Code-Remote #${testToken}] Claude Code Task Completed - Claude-Code-Remote`,
|
subject: `Re: [Claude-Code-Remote #${testToken}] Claude Code Task Completed - Claude-Code-Remote`,
|
||||||
text: 'Please explain the basic principles of quantum computing',
|
text: 'Please explain the basic principles of quantum computing',
|
||||||
replyTo: 'jiaxicui446@gmail.com'
|
replyTo: process.env.EMAIL_TO || process.env.ALLOWED_SENDERS
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,9 @@ class EmailChannel extends NotificationChannel {
|
||||||
pass: this.config.smtp.auth.pass
|
pass: this.config.smtp.auth.pass
|
||||||
},
|
},
|
||||||
// Add timeout settings
|
// Add timeout settings
|
||||||
connectionTimeout: 10000,
|
connectionTimeout: parseInt(process.env.SMTP_TIMEOUT) || 10000,
|
||||||
greetingTimeout: 10000,
|
greetingTimeout: parseInt(process.env.SMTP_TIMEOUT) || 10000,
|
||||||
socketTimeout: 10000
|
socketTimeout: parseInt(process.env.SMTP_TIMEOUT) || 10000
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.debug('Email transporter initialized');
|
this.logger.debug('Email transporter initialized');
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,12 @@ class DesktopChannel extends NotificationChannel {
|
||||||
// Try terminal-notifier first
|
// Try terminal-notifier first
|
||||||
try {
|
try {
|
||||||
const cmd = `terminal-notifier -title "${title}" -message "${message}" -sound "${sound}" -group "claude-code-remote"`;
|
const cmd = `terminal-notifier -title "${title}" -message "${message}" -sound "${sound}" -group "claude-code-remote"`;
|
||||||
execSync(cmd, { timeout: 3000 });
|
execSync(cmd, { timeout: parseInt(process.env.NOTIFICATION_TIMEOUT) || 3000 });
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fallback to osascript
|
// Fallback to osascript
|
||||||
const script = `display notification "${message}" with title "${title}"`;
|
const script = `display notification "${message}" with title "${title}"`;
|
||||||
execSync(`osascript -e '${script}'`, { timeout: 3000 });
|
execSync(`osascript -e '${script}'`, { timeout: parseInt(process.env.NOTIFICATION_TIMEOUT) || 3000 });
|
||||||
|
|
||||||
// Play sound separately
|
// Play sound separately
|
||||||
this._playSound(sound);
|
this._playSound(sound);
|
||||||
|
|
@ -63,7 +63,9 @@ class DesktopChannel extends NotificationChannel {
|
||||||
|
|
||||||
_sendLinux(title, message, sound) {
|
_sendLinux(title, message, sound) {
|
||||||
try {
|
try {
|
||||||
execSync(`notify-send "${title}" "${message}" -t 10000`, { timeout: 3000 });
|
const notificationTimeout = parseInt(process.env.NOTIFICATION_TIMEOUT) || 3000;
|
||||||
|
const displayTime = parseInt(process.env.NOTIFICATION_DISPLAY_TIME) || 10000;
|
||||||
|
execSync(`notify-send "${title}" "${message}" -t ${displayTime}`, { timeout: notificationTimeout });
|
||||||
this._playSound(sound);
|
this._playSound(sound);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -58,19 +58,31 @@ class ConfigManager {
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: 'email',
|
type: 'email',
|
||||||
enabled: false,
|
enabled: process.env.SMTP_USER ? true : false,
|
||||||
config: {
|
config: {
|
||||||
smtp: {
|
smtp: {
|
||||||
host: '',
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
port: 587,
|
port: parseInt(process.env.SMTP_PORT) || 587,
|
||||||
secure: false,
|
secure: process.env.SMTP_SECURE === 'true',
|
||||||
auth: {
|
auth: {
|
||||||
user: '',
|
user: process.env.SMTP_USER || '',
|
||||||
pass: ''
|
pass: process.env.SMTP_PASS || ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: '',
|
imap: {
|
||||||
to: []
|
host: process.env.IMAP_HOST || 'imap.gmail.com',
|
||||||
|
port: parseInt(process.env.IMAP_PORT) || 993,
|
||||||
|
secure: process.env.IMAP_SECURE !== 'false',
|
||||||
|
auth: {
|
||||||
|
user: process.env.IMAP_USER || process.env.SMTP_USER || '',
|
||||||
|
pass: process.env.IMAP_PASS || process.env.SMTP_PASS || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
from: process.env.EMAIL_FROM || `${process.env.EMAIL_FROM_NAME || 'Claude Code Remote'} <${process.env.SMTP_USER}>`,
|
||||||
|
to: process.env.EMAIL_TO || '',
|
||||||
|
template: {
|
||||||
|
checkInterval: parseInt(process.env.CHECK_INTERVAL) || 30
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
discord: {
|
discord: {
|
||||||
|
|
@ -125,7 +137,7 @@ class ConfigManager {
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(this.channelsConfigPath)) {
|
if (fs.existsSync(this.channelsConfigPath)) {
|
||||||
const fileChannels = JSON.parse(fs.readFileSync(this.channelsConfigPath, 'utf8'));
|
const fileChannels = JSON.parse(fs.readFileSync(this.channelsConfigPath, 'utf8'));
|
||||||
this._channels = { ...this._channels, ...fileChannels };
|
this._channels = this._deepMerge(this._channels, fileChannels);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn('Failed to load channels config:', error.message);
|
this.logger.warn('Failed to load channels config:', error.message);
|
||||||
|
|
|
||||||
|
|
@ -89,12 +89,11 @@ function isAllowed(fromAddress) {
|
||||||
return ALLOWED_SENDERS.some(allowed => addr.includes(allowed));
|
return ALLOWED_SENDERS.some(allowed => addr.includes(allowed));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract TaskPing token from subject
|
// Extract Claude-Code-Remote token from subject
|
||||||
function extractTokenFromSubject(subject = '') {
|
function extractTokenFromSubject(subject = '') {
|
||||||
const patterns = [
|
const patterns = [
|
||||||
/\[TaskPing\s+#([A-Za-z0-9_-]+)\]/,
|
/\[Claude-Code-Remote\s+#([A-Z0-9]+)\]/,
|
||||||
/\[TaskPing\s+([A-Za-z0-9_-]+)\]/,
|
/Re:\s*\[Claude-Code-Remote\s+#([A-Z0-9]+)\]/
|
||||||
/TaskPing:\s*([A-Za-z0-9_-]+)/i
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
|
|
@ -118,7 +117,7 @@ function cleanEmailText(text = '') {
|
||||||
line.includes('On') && line.includes('wrote:') ||
|
line.includes('On') && line.includes('wrote:') ||
|
||||||
line.includes('Session ID:') ||
|
line.includes('Session ID:') ||
|
||||||
line.includes('Session ID:') ||
|
line.includes('Session ID:') ||
|
||||||
line.includes('<noreply@pandalla.ai>') ||
|
line.includes(`<${process.env.SMTP_USER}>`) ||
|
||||||
line.includes('Claude-Code-Remote Notification System') ||
|
line.includes('Claude-Code-Remote Notification System') ||
|
||||||
line.includes('on 2025') && line.includes('wrote:') ||
|
line.includes('on 2025') && line.includes('wrote:') ||
|
||||||
line.match(/^>.*/) || // Quote lines start with >
|
line.match(/^>.*/) || // Quote lines start with >
|
||||||
|
|
@ -161,7 +160,7 @@ function cleanEmailText(text = '') {
|
||||||
|
|
||||||
// Skip remaining email quotes
|
// Skip remaining email quotes
|
||||||
if (trimmedLine.includes('Claude-Code-Remote Notification System') ||
|
if (trimmedLine.includes('Claude-Code-Remote Notification System') ||
|
||||||
trimmedLine.includes('<noreply@pandalla.ai>') ||
|
trimmedLine.includes(`<${process.env.SMTP_USER}>`) ||
|
||||||
trimmedLine.includes('on 2025')) {
|
trimmedLine.includes('on 2025')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test SMTP connection using environment variables
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
|
||||||
|
async function testSMTP() {
|
||||||
|
console.log('🔧 Testing SMTP connection...\n');
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
|
port: parseInt(process.env.SMTP_PORT) || 587,
|
||||||
|
secure: process.env.SMTP_SECURE === 'true',
|
||||||
|
auth: {
|
||||||
|
user: process.env.SMTP_USER,
|
||||||
|
pass: process.env.SMTP_PASS
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('📋 SMTP Configuration:');
|
||||||
|
console.log(` Host: ${config.host}`);
|
||||||
|
console.log(` Port: ${config.port}`);
|
||||||
|
console.log(` Secure: ${config.secure}`);
|
||||||
|
console.log(` User: ${config.auth.user}`);
|
||||||
|
console.log(` Pass: ${'*'.repeat(config.auth.pass?.length || 0)}\n`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const transporter = nodemailer.createTransport(config);
|
||||||
|
|
||||||
|
console.log('🔄 Verifying connection...');
|
||||||
|
await transporter.verify();
|
||||||
|
console.log('✅ SMTP connection successful!\n');
|
||||||
|
|
||||||
|
console.log('📧 Sending test email...');
|
||||||
|
const info = await transporter.sendMail({
|
||||||
|
from: process.env.EMAIL_FROM || `Claude Code Remote <${process.env.SMTP_USER}>`,
|
||||||
|
to: process.env.EMAIL_TO,
|
||||||
|
subject: 'Claude Code Remote - SMTP Test',
|
||||||
|
text: 'This is a test email from Claude Code Remote. If you receive this, SMTP is working correctly!'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Test email sent successfully!`);
|
||||||
|
console.log(`📧 Message ID: ${info.messageId}`);
|
||||||
|
console.log(`📬 To: ${process.env.EMAIL_TO}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ SMTP test failed:', error.message);
|
||||||
|
|
||||||
|
if (error.code === 'EAUTH') {
|
||||||
|
console.error('\n💡 Authentication failed. Please check:');
|
||||||
|
console.error(' - Gmail App Password is correct');
|
||||||
|
console.error(' - Two-factor authentication is enabled');
|
||||||
|
console.error(' - Email credentials in .env file');
|
||||||
|
} else if (error.code === 'ETIMEDOUT') {
|
||||||
|
console.error('\n💡 Connection timeout. Please check:');
|
||||||
|
console.error(' - Internet connection');
|
||||||
|
console.error(' - Firewall settings');
|
||||||
|
console.error(' - SMTP host and port');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testSMTP().catch(console.error);
|
||||||
Loading…
Reference in New Issue