I recently enabled email sending feature to Django with Sendgrid smtp as the backend
here are my email settings:
#AUTO SEND EMAIL
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_HOST_USER = 'apikey'
EMAIL_HOST_PASSWORD = 'the_sendgrid_key'
EMAIL_PORT = 587
In one of my api got the following codes:
student = request.user
subject = 'test',
text_content = 'test'
email = EmailMultiAlternatives(subject, text_content, os.environ.get('DEFAULT_FROM_EMAIL'), to=[student.email])
email.attach_alternative(html_content, "text/html")
email.send()
During local development whenever i call the api the email got sent and i received it normally
But when i called the api on the server it keep returning the following error:
SMTPServerDisconnected: please run connect() first (Most recent call last)
File /root/study/api/views/views.py line 1470 in post args locals
email.send()
File /usr/lib/python3.8/smtplib.py line 753 in starttls args locals
self.ehlo_or_helo_if_needed()
File /usr/lib/python3.8/smtplib.py line 604 in ehlo_or_helo_if_needed args locals
if not (200 <= self.ehlo()[0] <= 299):
File /usr/lib/python3.8/smtplib.py line 444 in ehlo args locals
self.putcmd(self.ehlo_msg, name or self.local_hostname)
File /usr/lib/python3.8/smtplib.py line 371 in putcmd args locals
self.send(str)
File /usr/lib/python3.8/smtplib.py line 363 in send args locals
raise SMTPServerDisconnected('please run connect() first')
SMTPServerDisconnected: please run connect() first
After looking up this error there only answers about incorrect email settings but i checked and the email settings is totally correct(because i received the email on local)
Another weird thing is when i run python manage.py shell
on server and called the email.send()
method directly no error pop up and i received the email.
I been looking for solution a week now and can't seem to find why the error only popping up when calling the api that contain the method, hope anyone having the similar problem can help me figure it out
UPDATE: i tried opening and closing manually in the view following the suggestions in the comments:
from django.core.mail import EmailMultiAlternatives, get_connection, EmailMessage
connection = get_connection()
# Manually open the connection
connection.open()
# Construct an email message that uses the connection
email1 = EmailMessage(
subject,
text_content,
os.environ.get('DEFAULT_FROM_EMAIL'),
to=[student.email],
connection=connection,
)
email1.send() # Send the email
connection.close()
but still getting the same connect() error:
SMTPServerDisconnected: please run connect() first (Most recent call last)
File /root/study/api/views/views.py line 1474 in post args locals
connection.open()
Show 1 non-project frame
File /usr/lib/python3.8/smtplib.py line 753 in starttls args locals
self.ehlo_or_helo_if_needed()
File /usr/lib/python3.8/smtplib.py line 604 in ehlo_or_helo_if_needed args locals
if not (200 <= self.ehlo()[0] <= 299):
File /usr/lib/python3.8/smtplib.py line 444 in ehlo args locals
self.putcmd(self.ehlo_msg, name or self.local_hostname)
File /usr/lib/python3.8/smtplib.py line 371 in putcmd args locals
self.send(str)
File /usr/lib/python3.8/smtplib.py line 363 in send args locals
raise SMTPServerDisconnected('please run connect() first')
SMTPServerDisconnected: please run connect() first
UPDATE 2:
My server enabled SSL, running on gunicorn with nginx to serve the static files and redirect the ports
gunicorn config:
[Unit]
Description=Gunicorn daemon for Django Project
Before=nginx.service
After=network.target
[Service]
WorkingDirectory=/root/study
ExecStart=/root/.cache/pypoetry/virtualenvs/base.django-H96T9Ltg-py3.8/bin/gunicorn --log-level=debug --access-logfile /var/log/gunicorn/access.log --error-logfile /var/log/gunicorn/error.log --workers 5 --bind unix:/var/log/gunicorn/hoola.sock base.wsgi:application
Restart=always
SyslogIdentifier=gunicorn
User=root
Group=www-data
[Install]
WantedBy=multi-user.target
nginx config:
user root;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}
UPDATE 5: more details error traceback from rollbar error logging:
SMTPServerDisconnected: please run connect() first (Most recent call last)
File /root/study/api/views/views.py line 1729 in get args locals
email.send()
get arguments | |
---|---|
"self" | "<class 'api.views.views.email_testing'>" |
"request" | "<class 'rest_framework.request.Request'>" |
get local variables | |
---|---|
e | "<class 'smtplib.SMTPServerDisconnected'>" |
"<class 'django.core.mail.message.EmailMultiAlternatives'>" | |
request | "<class 'rest_framework.request.Request'>" |
self | "<class 'api.views.views.email_testing'>" |
Hide 3 non-project frames
File /root/.cache/pypoetry/virtualenvs/base.django-H96T9Ltg-py3.8/lib/python3.8/site-packages/django/core/mail/message.py line 284 in send args locals
return self.get_connection(fail_silently).send_messages([self])
send arguments | |
---|---|
"self" | <class'django.core.mail.message.EmailMultiAlternatives'>" |
"fail_silently" | unknown |
send local variables | |
---|---|
fail_silently | false |
self | "<class 'django.core.mail.message.EmailMultiAlternatives'>" |
File /root/.cache/pypoetry/virtualenvs/base.django-H96T9Ltg-py3.8/lib/python3.8/site-packages/django/core/mail/backends/smtp.py line 102 in send_messages args locals
new_conn_created = self.open()
send_messages arguments | |
---|---|
"self" | "<class 'django.core.mail.backends.smtp.EmailBackend'>" |
"email_messages | [<class'django.core.mail.message.EmailMultiAlternatives'>"] |
send_messages local variables | |
---|---|
email_messages | ["<class 'django.core.mail.message.EmailMultiAlternatives'>"] |
self | "<class 'django.core.mail.backends.smtp.EmailBackend'>" |
File /root/.cache/pypoetry/virtualenvs/base.django-H96T9Ltg-py3.8/lib/python3.8/site-packages/django/core/mail/backends/smtp.py line 67 in open args locals
self.connection.starttls(keyfile=self.ssl_keyfile, certfile=self.ssl_certfile)
open arguments | |
---|---|
"self" | "<class 'django.core.mail.backends.smtp.EmailBackend'>" |
open local variables | |
---|---|
connection_params | {"local_hostname": "mydomain.io"} |
self | "<class 'django.core.mail.backends.smtp.EmailBackend'>" |
File /usr/lib/python3.8/smtplib.py line 753 in starttls args locals
starttls arguments | |
---|---|
"self" | "<class 'smtplib.SMTP'>" |
"keyfile" | unknown |
"certfile" | unknown |
"context" | inknown |
starttls local variables | |
---|---|
certfile | null |
context | null |
keyfile | null |
self | "<class 'smtplib.SMTP'>" |
File /usr/lib/python3.8/smtplib.py line 604 in ehlo_or_helo_if_needed args locals
if not (200 <= self.ehlo()[0] <= 299):
ehlo_or_helo_if_needed arguments | |
---|---|
"self" | "<class 'smtplib.SMTP'>" |
ehlo_or_helo_if_needed arguments | |
---|---|
self | "<class 'smtplib.SMTP'>" |
File /usr/lib/python3.8/smtplib.py line 444 in ehlo args locals
self.putcmd(self.ehlo_msg, name or self.local_hostname)
ehlo arguments | |
---|---|
"self" | "<class 'smtplib.SMTP'>" |
"name" | unknown |
ehlo local variables | |
---|---|
name | "" |
self | "<class 'smtplib.SMTP'>" |
File /usr/lib/python3.8/smtplib.py line 371 in putcmd args locals
self.send(str)
putcmd arguments | |
---|---|
"self" | "<class 'smtplib.SMTP'>" |
"cmd" | "ehlo" |
"args" | "mydomain.io" |
putcmd local variables | |
---|---|
args | "mydomain.io" |
cmd | "ehlo" |
self | "<class 'smtplib.SMTP'>" |
str | "ehlo mydomain.io\r\n" |
File /usr/lib/python3.8/smtplib.py line 363 in send args locals
raise SMTPServerDisconnected('please run connect() first')
send arguments | |
---|---|
"self" | "<class 'smtplib.SMTP'>" |
"s" | "ehlo mydomain.io\r\n" |
send local variables | |
---|---|
s | "ehlo mydomain.io\r\n" |
self | "<class 'smtplib.SMTP'>" |
SMTPServerDisconnected: please run connect() first
So i finally found out why it never seem to connect, it's because my entire project setup for gunicorn missing .env file variable. That why EMAIL_HOST and EMAIL configs never load when running in the views.
I added loadenv to my wsgi.py file so gunicorn can load it:
import os
from django.core.wsgi import get_wsgi_application
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env'))
#load env before running wsgi
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "base.settings")
application = get_wsgi_application()
Also changed how my gunicorn.service running command;
in ExecStart i changed from base.wsgi:application
to base.wsgi
so it also load the entire dotenv variable instead of just application