Guides / smtplib.SMTPAuthenticationError (535) in Django production

smtplib.SMTPAuthenticationError (535) in Django production

Password reset emails and contact forms work locally and fail in production with smtplib.SMTPAuthenticationError: (535, b'5.7.8 Username and Password not accepted'), or hang and time out. Django's email settings are fine for development but need deliberate values in production. The error code tells you whether it's a login problem or a connection problem.

What it means

Django hands the message to an SMTP server using EMAIL_HOST, EMAIL_PORT and your credentials. Authentication errors (535, 534) mean the server refused your login. Connection errors (timed out, Connection refused, Network is unreachable) mean it never got that far.

Why it happens

  1. You are using your normal password. Providers such as Gmail and Microsoft 365 don't accept an account password for SMTP any more. You need an app password, or the provider's SMTP credentials.
  2. TLS and port don't match. Port 587 uses STARTTLS (EMAIL_USE_TLS = True); port 465 uses implicit TLS (EMAIL_USE_SSL = True). Setting both raises ValueError: EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive.
  3. The cloud blocks outgoing SMTP. Many hosts throttle or block port 25 by default. Use 587 or 465 with a transactional provider.
  4. Credentials missing in production. The environment variables are not set, so Django tries localhost:25 and gets ConnectionRefusedError.
  5. The sender isn't allowed. DEFAULT_FROM_EMAIL is an address or domain the provider hasn't verified (SES, SendGrid, Postmark and others reject or drop it).
  6. The mail is accepted but lands in spam. Missing SPF, DKIM or DMARC records for your sending domain.

The fix

# settings.py
import os

EMAIL_BACKEND = os.environ.get("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
EMAIL_HOST = os.environ["EMAIL_HOST"]
EMAIL_PORT = int(os.environ.get("EMAIL_PORT", "587"))
EMAIL_HOST_USER = os.environ["EMAIL_HOST_USER"]
EMAIL_HOST_PASSWORD = os.environ["EMAIL_HOST_PASSWORD"]
EMAIL_USE_TLS = EMAIL_PORT == 587       # STARTTLS
EMAIL_USE_SSL = EMAIL_PORT == 465       # implicit TLS
EMAIL_TIMEOUT = 10                      # never let a request hang on SMTP
DEFAULT_FROM_EMAIL = os.environ["DEFAULT_FROM_EMAIL"]
SERVER_EMAIL = DEFAULT_FROM_EMAIL       # sender of error emails to ADMINS

Test it from the production environment, not from your laptop:

python manage.py sendtestemail you@example.com

Locally, avoid sending real mail: EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend prints messages to the terminal.

Checklist when it still fails: confirm the exact host and port in your provider's docs; check that outgoing traffic on that port is allowed from your server (nc -zv smtp.example.com 587); verify your sending domain in the provider's dashboard; and add the SPF and DKIM DNS records they give you.

Making it stick in production

Or skip it

Django Cloud stores your settings, such as your SMTP password, encrypted and never shows them again. Add them in the dashboard or with djangocloud env push and they apply to the next release.

pip install djangocloud-cli
djangocloud deploy

Get startedSee pricing

Free plans are available for students, AWS Activate startups, open-source, non-profit and research projects. See how Django Cloud compares with other ways to host Django.

Django is a registered trademark of the Django Software Foundation. Django Cloud is not affiliated with or endorsed by it. Found a mistake in this guide? Tell us and we will fix it.