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

Django集成SendGrid邮件订阅遇401未授权及唯一约束错误排查

问题排查与解决方法

你遇到了两个独立的问题:唯一约束冲突错误和SendGrid未授权错误,我来逐一帮你分析解决:


一、django.db.utils.IntegrityError: UNIQUE constraint failed: home_subscriber.email

原因

你的Subscriber模型中email字段设置了unique=True,数据库不允许存在重复的邮箱记录。当用户重复提交同一个邮箱时,你的代码直接创建新的Subscriber实例并调用save(),就会触发这个唯一约束冲突。

解决方法

修改subscribe视图,在创建新实例前先检查邮箱是否已存在,根据不同情况处理:

import random
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
from .models import Subscriber
from .forms import SubscriberForm
from django.conf import settings

def random_digits():
    return "%0.12d" % random.randint(0, 999999999999)

@csrf_exempt
def subscribe(request):
    if request.method == 'POST':
        email = request.POST['email']
        # 尝试获取已存在的订阅记录,不存在则创建
        sub, created = Subscriber.objects.get_or_create(email=email)
        
        if not created:
            # 邮箱已存在的情况
            if sub.confirmed:
                return render(request, 'sub.html', {
                    'email': email, 
                    'action': 'already_confirmed', 
                    'form': SubscriberForm()
                })
            else:
                # 未确认,重新生成确认码
                sub.conf_num = random_digits()
                sub.save()
        else:
            # 新创建的订阅记录,设置确认码
            sub.conf_num = random_digits()
            sub.save()
        
        # 统一发送确认邮件
        message = Mail(
            from_email=settings.FROM_EMAIL,
            to_emails=sub.email,
            subject='Newsletter Confirmation',
            html_content='Thank you for signing up for my email newsletter! \
            Please complete the process by \
            <a href="{}/confirm/?email={}&conf_num={}"> clicking here to \
            confirm your registration</a>.'.format(request.build_absolute_uri('/confirm/'), sub.email, sub.conf_num))
        
        try:
            sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
            response = sg.send(message)
        except Exception as e:
            return render(request, 'sub.html', {
                'email': sub.email, 
                'action': 'send_failed', 
                'form': SubscriberForm()
            })
        
        action = 'added' if created else 'resend_confirmation'
        return render(request, 'sub.html', {
            'email': sub.email, 
            'action': action, 
            'form': SubscriberForm()
        })
    else:
        return render(request, 'sub.html', {'form': SubscriberForm()})

这段代码用get_or_create避免重复创建记录,同时处理两种场景:已确认的邮箱直接提示用户,未确认的邮箱重新发送确认邮件。


二、python_http_client.exceptions.UnauthorizedError: HTTP Error 401: Unauthorized

原因

这个错误是SendGrid拒绝了你的API请求,常见触发原因有:

  1. API密钥未正确加载:环境变量SENDGRID_API_KEY未配置或加载失败,导致请求时没有携带有效密钥。
  2. 发件邮箱未验证:你设置的FROM_EMAIL没有在SendGrid控制台完成验证(单个邮箱或域名验证),SendGrid会拦截未验证的发件请求。
  3. API密钥权限不足:创建密钥时未授予Mail Send权限,导致无法调用邮件发送接口。

解决步骤

  1. 确认API密钥是否有效加载:
    在视图中临时添加打印语句,检查密钥是否获取到:
    print("Loaded SendGrid API Key:", settings.SENDGRID_API_KEY)
    
    如果输出为空,说明环境变量配置有问题,可以临时在settings.py中硬编码密钥测试(测试完成后务必改回环境变量方式):
    SENDGRID_API_KEY = "你的真实SendGrid API密钥"
    
  2. 验证发件邮箱:
    登录SendGrid控制台,进入Settings > Sender Authentication,选择Single Sender Verification,添加并验证你的FROM_EMAIL地址。
  3. 检查API密钥权限:
    进入SendGrid的Settings > API Keys,找到你使用的密钥,确保它拥有Mail Send的权限(可以设置为Full Access或仅勾选Mail Send的Write权限)。

额外优化建议

  • 移除csrf_exempt装饰器,改用Django表单的CSRF保护机制,提升网站安全性。
  • 在前端表单添加邮箱重复提交的验证(比如用AJAX提前检查邮箱是否已存在),减少后端错误触发。
  • 所有邮件发送操作都包裹在try-except块中,避免因邮件服务故障导致整个请求崩溃。

内容的提问来源于stack exchange,提问作者Eman5648

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 09:17:42