使用EmailJS与Next.js实现邮箱确认邮件发送功能失败的求助
EmailJS与Next.js实现邮箱确认邮件发送功能失败的求助
问题描述
我正在用表单结合EmailJS做邮箱验证,把邮箱值传给Next.js的API路由。在API的route.js里,我生成并哈希了token,存在Redis里,也生成了确认链接(目前控制台日志显示这部分都没问题),但调用sendEmail函数时收到错误:
Email send failed: undefined portfolio3d-nextjs-app-1 | Failed to send email: Error: Failed to send email portfolio3d-nextjs-app-1 | at sendEmail (webpack-internal:"另外,我是用Docker镜像运行项目的,有没有人能给我一些解决建议?
相关代码
前端表单组件(EmailConfirmationForm.js)
'use client'; import React from 'react'; import { useForm } from 'react-hook-form'; import { Toaster, toast } from 'sonner'; import { MailCheck, MailX } from 'lucide-react'; export default function EmailConfirmationForm() { const { register, handleSubmit, formState: { errors }, reset } = useForm(); // Send confirmation email const handleSendConfirmationEmail = async (email) => { try { const response = await fetch(`/api/sendConfirmationEmail?email=${email}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email }), }); if (!response.ok) throw new Error('Failed to send confirmation email'); toast.success('Confirmation email sent!', { duration: 5000, icon: <MailCheck />, style: { backgroundColor: '#1B1B1B', border: 'none', }, }); return true; } catch (error) { toast.error('Failed to send confirmation email.', { duration: 5000, icon: <MailX />, style: { backgroundColor: '#1B1B1B', border: 'none', }, }); return false; } }; const onSubmit = async (data) => { try { await handleSendConfirmationEmail(data.email); reset(); } catch (error) { console.error('Failed to send confirmation email:', error); } }; return ( <> <Toaster position="bottom-left" richColors /> <form onSubmit={handleSubmit(onSubmit)} className="max-w-md w-full flex flex-col items-center justify-center space-y-4"> {/* Email input */} <label htmlFor="email" className="self-start">Email Confirmation</label> <input id="email" type="email" placeholder="Email Confirmation" {...register("email", { required: 'This field is required', pattern: { value: /^\S+@\S+$/i, message: 'Invalid email format' } })} className="w-full p-2 rounded-md shadow-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent/50 custom-bg" aria-label="Email" /> {errors.email && <span className="inline-block self-start text-red-500">{errors.email.message}</span>} <input value="Cast your message!" className="px-10 py-4 rounded-md shadow-lg bg-background border border-accent/30 hover:shadow-glass-sm backdrop-blur-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent/50 cursor-pointer capitalize" type="submit" /> </form> </> ); }
Next.js API路由(sendConfirmationEmail/route.js)
import { NextResponse } from 'next/server'; import { sendEmail } from '@/app/../../service/service.email'; import redisClient, { connectRedis } from '@/app/../../service/redisClient'; import { generateToken, hashToken } from '@/app/../../service/tokenService'; import { rateLimiter } from '@/app/../../service/rateLimiter'; export async function POST(req) { const { searchParams } = new URL(req.url); const email = searchParams.get('email'); const ip = req.headers.get('x-forwarded-for') || req.socket.remoteAddress || req.ip; // if (!rateLimiter(ip)) { // return NextResponse.json({ message: 'Too many requests, please try again later.' }, { status: 429 }); // } await connectRedis(); const token = generateToken(); const hashedToken = await hashToken(token); const expiresAt = Date.now() + 3600 * 1000; try { await redisClient.setEx(`confirm_tokens:${hashedToken}`, 3600, JSON.stringify({ email, expiresAt })); const storedToken = await redisClient.get(`confirm_tokens:${hashedToken}`); console.log('Token stored in Redis:', storedToken); // Log to verify storage } catch (redisError) { console.error('Error saving token in Redis:', redisError); return NextResponse.json({ message: 'Error saving confirmation token to Redis.' }, { status: 500 }); } const confirmationLink = `${process.env.NEXT_PUBLIC_APP_URL}/api/confirmEmail?token=${token}&email=${email}`; console.log(`Confirmation link: ${confirmationLink}`); try { const templateParams = { to: email, from_name: 'Email Confirmation', reply_to: email, message: `Please confirm your email by clicking the link: ${confirmationLink}`, }; await sendEmail(templateParams); return NextResponse.json({ message: 'Confirmation email sent!' }, { status: 200 }); } catch (error) { console.error('Failed to send email:', error); return NextResponse.json({ message: 'Failed to send confirmation email.' }, { status: 500 }); } }
EmailJS发送服务(service.email.js)
import emailjs from '@emailjs/browser'; export const sendEmail = async (params) => { try { const response = await emailjs.send( process.env.NEXT_PUBLIC_SERVICE_ID, process.env.NEXT_PUBLIC_TEMPLATE_ID, params, { publicKey: process.env.NEXT_PUBLIC_PUBLIC_KEY, limitRate: { throttle: 5000, }, } ); console.log('Email sent successfully:', response); return response; } catch (error) { console.error('Email send failed:', error.text); if (error.response) { console.error('Error response from email service:', error.response); } throw new Error(error.text || 'Failed to send email'); } };
排查建议
我帮你梳理几个可能的问题点,你可以逐一排查:
- Docker环境变量加载检查:你用Docker运行项目,要确认
NEXT_PUBLIC_SERVICE_ID、NEXT_PUBLIC_TEMPLATE_ID、NEXT_PUBLIC_PUBLIC_KEY这些环境变量是否正确注入容器。如果是用.env文件,要确保它被正确挂载到容器内,或者在docker-compose.yml里配置了对应的环境变量,环境变量缺失会直接导致EmailJS初始化失败,出现error.text为undefined的情况。 - 修复错误处理逻辑:当前
sendEmail函数里打印error.text,但EmailJS的错误对象结构可能并非如此。建议先打印完整的error对象,获取更详细的错误信息:catch (error) { console.error('Email send failed:', error); // 打印完整错误对象 if (error.response) { console.error('Error response from email service:', error.response); } throw new Error(error.message || error.text || 'Failed to send email'); } - EmailJS模板参数匹配检查:你传入的
templateParams变量名(to、from_name等)要和EmailJS后台创建的模板里的变量完全一致(包括大小写),如果模板里用的是{{to_email}}而不是{{to}},会导致参数不匹配触发错误。 - Docker网络连通性检查:EmailJS需要调用外部API,要确保Docker容器能正常访问外网。可以进入容器内部执行
ping api.emailjs.com测试网络是否通畅,网络受限也会导致请求失败返回undefined错误。 - API路由参数获取逻辑统一:当前API路由同时从query参数和请求body获取邮箱,建议统一方式,比如只从body解析:
避免潜在的参数获取异常。const { email } = await req.json();
备注:内容来源于stack exchange,提问作者Zvezdi7205885
相关产品推荐
相关产品推荐

