Next.js中用SendGrid实现表单发邮件遇401未授权错误求助
解决Next.js + SendGrid 401未授权错误
问题场景
我在Next.js中创建一个报价请求表单,使用SendGrid作为第三方API发送邮件/提交表单报价请求,但一直收到401未授权错误,无法将表单与邮件功能关联。
错误信息
error - ResponseError: Unauthorized at node_modules/@sendgrid/client/src/classes/client.js:146:29 at processTicksAndRejections (node:internal/process/task_queues:96:5) { code: 401, response: { headers: { server: 'nginx', date: 'Wed, 18 Jan 2023 17:25:46 GMT', 'content-type': 'application/json', 'content-length': '116', connection: 'close', 'access-control-allow-origin': 'https://sendgrid.api-docs.io', 'access-control-allow-methods': 'POST', 'access-control-allow-headers': 'Authorization, Content-Type, On-behalf-of, x-sg-elas-acl', 'access-control-max-age': '600', 'x-no-cors-reason': 'https://sendgrid.com/docs/Classroom/Basics/API/cors.html', 'strict-transport-security': 'max-age=600; includeSubDomains' }, body: { errors: [Array] } }, page: '/api/contact' }
相关代码
pages/contact.jsx
const contact = () => { const [values, setValues] = useState({ firstName: "", lastName: "", phone: "", email: "", zip: "", message: "", }); const {firstName, lastName, phone, email, zip, message} = values; const handleChange = e => setValues({ ...values, [e.target.name]: e.target.value }); const handleSubmit = async (e) => { e.preventDefault() try { await fetch("http://localhost:3000/api/contact", { method: "POST", header: { "Content-Type": "application/json", }, body: JSON.stringify(values), }); } catch (err) { console.log(err); } }; return ( <div className='w-full h-screen'> <div className='min-h-screen py-40 border border-gray-800 bg-white bg-gradient-to-br from-sjrblue to-sjrblue-200'> <div className="container mx-auto"> <div className="flex flex-col lg:flex-row w-10/12 bg-white rounded-xl mx-auto shadow-lg overflow-hidden"> <div alt='loaded trailer' className='w-full h-[400px] lg:gap-5 lg:h-auto lg:w-1/2 bg-contactImage bg-cover bg-center text-center shadow-lg shadow-slate-400'></div> {/* Request Quote Form */} <div className="lg:w-1/2 lg:m-2 p-4 items-center"> <h2 className='text-3xl mb-4 text-center text-sjrblue'>Company Name</h2> <p className='mb-4 text-center'>CONTACT US TODAY FOR A <span className='text-blue font-bold'>FREE</span> JUNK REMOVAL ESTIMATE</p> <form onSubmit={handleSubmit}> <div className='grid grid-cols-2 gap-1 md:gap-3 lg:gap-5'> <input type="text" placeholder='first name' value={firstName} onChange={handleChange} required name='firstName' className='border border-gray-400 py-1 px-2 shadow-lg shadow-slate-400' /> <input type="text" placeholder='sur name' value={lastName} onChange={handleChange} required name='lastName' className='border border-gray-400 py-1 px-2 shadow-lg shadow-slate-400' /> </div> <div className='mt-5'> <input type="email" placeholder='email' value={email} onChange={handleChange} required name='email' className='text-sm border border-gray-400 py-1 px-2 w-full shadow-lg shadow-slate-400' /> </div> <div className='mt-5'> <input type="tel" id='phone-number' onkeydown='phoneNumberFormatter()' inputmode='tel' placeholder='phone number 123-456-7890' pattern='[0-9]{3}-[0-9]{3}-[0-9]{4}' value={phone} onChange={handleChange} required name='phone' className='text-sm border border-gray-400 py-1 px-2 w-full shadow-lg shadow-slate-400' /> </div> <div className='mt-5'> <input type="text" inputmode='numeric' placeholder='zip code' maxLength={5} value={zip} onChange={handleChange} name='zip' required className='text-sm border border-gray-400 py-1 px-2 w-full shadow-lg shadow-slate-400' /> </div> <div className='mt-5'> <textarea name="message" placeholder='message' id="" cols="30" rows="10" value={message} onChange={handleChange} required className='text-sm border border-gray-400 py-1 px-2 w-full shadow-lg shadow-slate-400'> </textarea> </div> <div className='mt-5 text-center'> <input type="checkbox" required className='border border-gray-400 w-3 h-3'/> <span className='px-3 text-sm'>I am not a robot</span> <div className='mt-5'> <button className='bg-sjrblue w-full text-enter text-white py-3 rounded hover:bg-blue-700 duration-300 shadow-lg shadow-slate-400'>Get your FREE estimate</button> </div> </div> </form> </div> </div> </div> </div> ); }; export default contact;
pages/api/contact.js
require("dotenv").config(); const sgMail = require("@sendgrid/mail"); const { SG_API_KEY, FROM_EMAIL, TO_EMAIL } = process.env; sgMail.setApiKey(SG_API_KEY); export default async function handler(req, res) { const {firstName, lastName, phone, email, zip, message} = req.body; const msg = { to: TO_EMAIL, from: FROM_EMAIL, subject: '', html: `<p><strong>First Name: </strong>${firstName}</p> <p><strong>Last Name: </strong>${lastName}</p> <p><strong>Phone Number: </strong>${phone}</p> <p><strong>Email: </strong>${email}</p> <p><strong>Zip: </strong>${zip}</p> <p><strong>Message: </strong>${message}</p> `, }; await sgMail.send(msg); console.log('email sent'); res.status(200).json({ success: true }); }
根目录.env文件
SG_API_KEY= #API Key TO_EMAIL= #verified email FROM_EMAIL= #email to send form to
排查与解决步骤
验证API密钥正确性
确保.env中的SG_API_KEY是从SendGrid控制台生成的完整密钥,无多余空格或换行。创建密钥时需勾选「Mail Send」权限,或直接选择「Full Access」。确认环境变量加载正常
在pages/api/contact.js中添加console.log(SG_API_KEY),启动项目后查看控制台输出:- 若输出
undefined,检查.env是否在项目根目录,文件名是否为.env(注意开头的点) - 修改
.env后需重启Next.js开发服务器,环境变量才会生效 - 此API路由无需
NEXT_PUBLIC_前缀,直接使用变量名即可
- 若输出
验证邮箱合法性
SendGrid要求FROM_EMAIL必须是已验证的邮箱或域名,免费版用户的TO_EMAIL也需要提前验证,否则会触发权限拦截。修复代码细节问题
- 前端fetch请求修正:
header拼写错误改为headers,使用相对路径避免环境差异:await fetch("/api/contact", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(values), }); - 后端添加错误捕获:避免SendGrid报错导致API路由崩溃,同时输出详细错误信息:
export default async function handler(req, res) { try { const {firstName, lastName, phone, email, zip, message} = req.body; const msg = { to: TO_EMAIL, from: FROM_EMAIL, subject: `新报价请求:${firstName} ${lastName}`, html: `<p><strong>First Name: </strong>${firstName}</p> <p><strong>Last Name: </strong>${lastName}</p> <p><strong>Phone Number: </strong>${phone}</p> <p><strong>Email: </strong>${email}</p> <p><strong>Zip: </strong>${zip}</p> <p><strong>Message: </strong>${message}</p> `, }; await sgMail.send(msg); console.log('email sent'); res.status(200).json({ success: true }); } catch (error) { console.error('SendGrid错误详情:', error.response?.body?.errors || error.message); res.status(error.code || 500).json({ success: false, error: error.message }); } } - .env文件格式修正:注释与变量值分开,避免解析错误:
# SendGrid API密钥 SG_API_KEY=你的完整API密钥 # 收件邮箱(需验证) TO_EMAIL=xxx@example.com # 发件邮箱(需验证) FROM_EMAIL=yyy@example.com
- 前端fetch请求修正:
内容的提问来源于stack exchange,提问作者user17300632
相关产品推荐
相关产品推荐

