10

I have a script that I need to run indefinitely. The script is set to e-mail me confirmations of certain steps being completed on a daily basis. I am trying to use smtplib for this.

The initial connection is set up so that I will enter my login (written into the script) and a password using getpass at the very initiation of the script. I do not want to leave my password written into the script or even reference by the script say in a config file. Therefore, I want to enter the password at initiation and leave the smtp connection in place.

Re-connecting to the smtp connection as required in the script would defeat the point of being able to step away from the script entirely and leave it running indefinitely.

The example code that I am working with at the moment looks like this:

import smtplib
import getpass

smtpObj = smtplib.SMTP('smtp.gmail.com',587)
smtpObj.ehlo()
smtpObj.starttls()
smtpObj.login('myemail@gmail.com',password = getpass.getpass('Enter Password: '))

Then I enter the password and the output is:

(235, b'2.7.0 Accepted')

So this all works fine.

The problem is then that the script needs to pause for anywhere from a few minutes to potentially a few days depending on the time. This is achieved using a while loop with time conditions until a certain time when the send function will be called:

smtpObj.sendmail('myemail@gmail.com','recipient@gmail.com','This is a test')

However, after a period of about 20/30 mins it seems (i.e. if the pause is sufficient). Then the smptObj.sendmail call will fail due to a timeout error.

The specific error is as follows:

SMTPSenderRefused: (451, b'4.4.2 Timeout - closing connection. l22sm2469172wre.52 - gsmtp', 'myemail@gmail.com')

I have so far tried the following:

Instantiating the connection object with the following timeout parameterisation:

smtpObj = smtplib.SMTP('smtp.gmail.com',587,timeout=None)
smtpObj = smtplib.SMTP('smtp.gmail.com',587,timeout=86400)

Neither of these seem to supress the 'timeout' of the connection (i.e. the same problem persists).

I have also tried this solution approach suggested in this post:

How can I hold a SMTP connection open with smtplib and Python?

However, this has not worked either!

I do want to try and avoid the solution where I would have to re-connect each time when I wanted to send the e-mail, because I only want to enter the password for the connection the once manually, rather than writing it into the script either directly or indirectly.

There surely is a way to deal with the timeout issue! If anyone can help here, then please let me know! Though, if you think that the more 'obvious' solution of re-connecting just before the script needs to send an e-mail is the better way to go, then please let me know.

Thank you!...

agftrading
  • 784
  • 3
  • 8
  • 21

1 Answers1

10

If you don't want to include sensitive credentials in a script, you should use env vars.

From a terminal shell (outside of python):

$ EXPORT secretVariable=mySecretValue
$ echo $secretVariable
$ mySecretValue
$

So to leverage this in your code...

>>> import os
>>> myPW = os.getenv('secretVariable')
>>> myPW
'mySecretVal'
>>>

By doing this, you don't have to manually type in the password. Beyond that, it's not very practical to attempt to leave an idle SMTP connection open for potentially days at a time, just implement a try/except structure..

import smtplib
import os

def smtp_connect():
    # Instantiate a connection object...
    password = os.getenv('secretVariable')
    smtpObj = smtplib.SMTP('smtp.gmail.com',587)
    smtpObj.ehlo()
    smtpObj.starttls()
    smtpObj.login('myemail@gmail.com',password=password)
    return smtpObj

def smtp_operations():
    try:
        # SMTP lib operations...
        smtpObj.sendmail('myemail@gmail.com','recipient@gmail.com','This is a test')
        # SMTP lib operations...

    except Exception: # replace this with the appropriate SMTPLib exception

        # Overwrite the stale connection object with a new one
        # Then, re-attempt the smtp_operations() method (now that you have a fresh connection object instantiated).
        smtpObj = smtp_connect() 
        smtp_operations()

smtpObj = smtp_connect()
smtp_operations()

By replacing except Exception with the actual SMTP Exception that gets raised when you have a stale connection, you'll be sure you're not catching exceptions that don't pertain to the connection being stale.

So, using try/except, the script will attempt to preform the SMTP operations. If the connection is stale, it will instantiate a fresh connection object and then attempt to re-execute itself with the fresh connection object.

Ephexx
  • 311
  • 4
  • 14
  • Thanks for the swift reply. I should add that the script would be running on a cloud instance and I want to avoid leaving the password stored on the cloud instance.I think that this is what would be required with your suggestion of using the env vars and referencing them in the code. In that case, even though the password would not be in the code, it would still be stored alongside the code. – agftrading Mar 10 '18 at 00:25
  • The try / except explanation is helpful though. Upvoted, but not enough reputation yet! Thank you. – agftrading Mar 10 '18 at 00:29