You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 09:08:13