Search code examples
pythondjangosmtpsendgriddjango-email

only getting SMTPServerDisconnected: please run connect() first when calling on server


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'>"
email "<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

Solution

  • 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