NestJS用Nodemailer发验证OTP时遇ESOCKET-connect错误
NestJS使用Nodemailer+Gmail SMTP发送OTP时出现ESOCKET-connect错误
配置与代码详情
邮件器配置(app.module)
MailerModule.forRootAsync({ useFactory: (configService: ConfigService) => configService.get('mailer'), inject: [ConfigService], }),
mailer.config
export default () => ({ mailer: { transport: { host: process.env.MAIL_HOST, port: parseInt(process.env.MAIL_PORT), auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PASSWORD, }, }, defaults: { from: process.env.MAIL_FROM, }, }, });
register-email.provider
import { ConflictException, Injectable } from '@nestjs/common'; import { FindOneByEmailProvider } from 'src/users/providers/find-one-by-email.provider'; // import { v4 as uuidv4 } from 'uuid'; import { MailerService } from '@nestjs-modules/mailer'; import { OtpProvider } from './otp.provider'; import { EmailDto } from '../dto/email.dto'; @Injectable() export class RegisterEmailProvider { constructor( private readonly findOneByEmailProvider: FindOneByEmailProvider, private readonly otpProvider: OtpProvider, private readonly mailerService: MailerService, ) { // 定时清理过期OTP setInterval(() => this.otpProvider.cleanUpExpiredOtps(), 60 * 1000); // 每分钟执行一次 } public async registerEmail(registerEmailDto: EmailDto) { const { email } = registerEmailDto; const existingUser = await this.findOneByEmailProvider.findOneByEmailProvider(email); if (existingUser && existingUser.isActive === true) { throw new ConflictException('Email already exists', { description: 'Email already exists', }); } // 生成6位数字OTP const otp = Math.floor(100000 + Math.random() * 900000).toString(); const expiresAt = Date.now() + 15 * 60 * 1000; // 15分钟后过期 this.otpProvider.setOtp(email, otp, expiresAt); console.log('OTP:', otp); // 发送OTP邮件 try { await this.mailerService.sendMail({ to: email, subject: 'Email Verification OTP', text: `Your OTP for email verification is: ${otp}`, }); console.log('OTP sent to email'); return { message: 'OTP sent to email' }; } catch (error) { throw new ConflictException(error, 'Failed to send OTP email'); } } }
错误信息
{ "errno": -4039, "code": "ESOCKET", "syscall": "connect", "address": "209.85.202.108", "port": 587, "command": "CONN" }
环境变量配置
# mailer configuration MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USER=my_email_account MAIL_PASSWORD='xxxx xxxx xxxx xxxx' # Gmail应用专用密码 MAIL_FROM='"Sender" <no-reply@sender.com>'
原因分析与解决方法
1. 网络连接受阻
ESOCKET错误核心是无法建立到Gmail SMTP服务器的TCP连接,排查方向:
- 用
telnet smtp.gmail.com 587测试连通性,若无法连接,说明防火墙、代理或网络策略拦截了587端口的出站流量。 - 云服务器环境需检查安全组/防火墙规则,确保允许587端口的出站请求;本地开发则检查杀毒软件、系统防火墙是否拦截了Nodemailer的连接。
2. 缺失TLS加密配置
Gmail 587端口要求使用STARTTLS加密,当前配置未明确开启,需修改mailer.config的transport配置:
transport: { host: process.env.MAIL_HOST, port: parseInt(process.env.MAIL_PORT), secure: false, // 587端口设为false,465端口设为true requireTLS: true, // 强制启用STARTTLS加密 auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PASSWORD, }, },
3. 应用专用密码无效
确认Gmail账号已开启2步验证,且应用专用密码正确:
- 登录Google账号「安全」设置,确认2步验证处于启用状态。
- 重新生成应用专用密码,环境变量中不要给密码加多余引号(改为
MAIL_PASSWORD=xxxx xxxx xxxx xxxx)。
4. DNS解析异常
若域名解析失败,可临时用Gmail SMTP的IP地址替换域名测试:
host: '209.85.202.108', // 替换原smtp.gmail.com
内容的提问来源于stack exchange,提问作者Ahmed Mahmud
相关产品推荐
相关产品推荐

