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

Django批量发邮件后触发smtplib.SMTPServerDisconnected错误

Hey there, let's tackle this smtplib.SMTPServerDisconnected issue you're hitting when sending bulk emails via Gmail with Django and Celery. The problem likely stems from how you're handling SMTP connections combined with Gmail's rate limits—even if you didn't find explicit docs, Gmail does enforce restrictions to prevent abuse. Here's how to fix it:

1. Stop creating a new SMTP connection for every email

Right now, your loop calls email.send() for each recipient, which opens a fresh SMTP connection to Gmail, sends one email, then closes it. Doing this 80+ times in quick succession triggers Gmail's anti-abuse safeguards, which will drop the connection to block what it sees as suspicious activity.

Fix: Reuse a single SMTP connection for all emails in the task using Django's get_connection():

from django.core.mail import get_connection
import smtplib

@shared_task(bind=True, max_retries=3)
def email_dispatch(self, heading, text):
    recipients = TheUser.objects.filter(subscription=True)
    # Create a persistent connection to reuse
    connection = get_connection()
    
    try:
        connection.open()  # Open the connection once
        emails = []
        for recipient in recipients:
            try:
                html_content = render_to_string('mails/email_dispatch.html', {'text': text})
                text_content = strip_tags(html_content)
                subject = f'{heading}'
                # Attach the shared connection to each email
                email = EmailMultiAlternatives(
                    subject, 
                    text_content, 
                    to=[recipient.id_user.email],
                    connection=connection
                )
                email.attach_alternative(html_content, 'text/html')
                emails.append(email)
            
            except NoReverseMatch:
                logger.info(f'Unexpected username: "{recipient.id_user.username}"')
        
        # Send all emails in one batch
        connection.send_messages(emails)
        logger.info(f'Successfully sent {len(emails)} emails')
    
    except smtplib.SMTPServerDisconnected as e:
        logger.error(f'Connection dropped unexpectedly: {e}')
        # Retry the task after a short delay to let Gmail reset
        self.retry(exc=e, countdown=10)
    
    finally:
        # Ensure the connection is closed properly
        connection.close()
    
    logger.info('Email dispatching has been finished.')

2. Respect Gmail's Rate Limits

Gmail enforces strict sending limits to prevent spam:

  • Free Gmail accounts: Up to 500 emails per day, with a rate cap of ~100 emails per minute.
  • Workspace accounts: Higher limits, but still have per-minute caps.

Fix: Split your bulk send into smaller Celery tasks to stay under these limits. For example, send 20 emails per task:

@shared_task
def send_batch_emails(heading, text, recipient_ids):
    recipients = TheUser.objects.filter(id__in=recipient_ids, subscription=True)
    connection = get_connection()
    
    try:
        connection.open()
        emails = []
        for recipient in recipients:
            html_content = render_to_string('mails/email_dispatch.html', {'text': text})
            text_content = strip_tags(html_content)
            subject = f'{heading}'
            email = EmailMultiAlternatives(
                subject, 
                text_content, 
                to=[recipient.id_user.email],
                connection=connection
            )
            email.attach_alternative(html_content, 'text/html')
            emails.append(email)
        
        connection.send_messages(emails)
        logger.info(f'Sent {len(emails)} emails in batch')
    
    finally:
        connection.close()

@shared_task
def email_dispatch(heading, text):
    recipient_ids = list(TheUser.objects.filter(subscription=True).values_list('id', flat=True))
    # Split into batches of 20
    batch_size = 20
    batches = [recipient_ids[i:i+batch_size] for i in range(0, len(recipient_ids), batch_size)]
    
    for batch in batches:
        send_batch_emails.delay(heading, text, batch)
    
    logger.info('Email dispatch batches queued.')

3. Verify Gmail Account Settings

If you're using a regular Gmail account:

  • If you have 2FA enabled, generate an App Password and use that as EMAIL_HOST_PASSWORD (your regular password won't work here).
  • If you don't have 2FA, enable "Less secure app access" (note: Google is phasing this out, so App Passwords are the better long-term solution).

4. Add Retry Logic for Temporary Failures

The bind=True and max_retries parameters in the first code example let Celery automatically retry the task if the connection drops temporarily, which helps with transient Gmail errors.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:10:13