Simple Python SMTP mail script – Part 2

Part 1 explained just why I wanted to send a SMTP email from the command line and explained some of the constraints I was under; this is how I got there.

I started out by not really being that familiar with Python, but I will always contend that knowing how to program is independent of the choice of language, so I wasn’t worried.

Fortunately (?), the very old version of Python on my shared hosting meant that I didn’t need to concern myself with the Python 2 vs Python 3 debate1, or which library to choose for some functions (no choice) so I could just get on with it.

Since I wanted to make this a general-purpose tool, I spent some extra time getting SSL on ports 465 and 587 working as well as non-encrypted on port 25. My laptop has the latest version of Python 2.7, so I could test these even if I couldn’t use them on TSOHost.

Here is the script:

#!/usr/bin/env python
#
# Simple smtp mailer program. Designed to work on old versions of Python (2.4).
# Has lots of command line parameters, but intended to run from shell scripts
# so doesn't matter. Tested on ports 25, 465 (encrypted) and 587 (encrypted).
# Only supports plain text auth via username & password.
# The encrypted modes require 2.6 or later as smtplib.SMTP_SSL is not present
# in earlier versions.
#
import smtplib
import datetime, sys, getopt, re
#import email.utils not on tsohost
#from email.mime.text import MIMEText not on tsohost
#SMTP_SSL not in tohost so port 465 and 587 won't work

port = ''
host = ''
mfrom = ''
mto = ''
msubj = ''
username = ''
password = ''
force_encrypt = False
quiet = False

def usage(progName = ''):
    print("Usage: %s -p port -o host -f mailFrom -t mailTo -s subject [opts] 'messageBody'" \
          % progName)
    print("Other options:")
    print("-u username -w password : Where login is required")
    print("-e : Force encryption - prevent plaintext username/password if not encypted")
    print("-q : Quiet")
    print("-h : Help")
    print("messageBody will be taken from stdin if parameter not present")
    
#get command line options    
try:
    myopts, args = getopt.getopt(sys.argv[1:],"p:o:f:t:s:u:w:eqh")
except getopt.error,e:
    print(str(e))
    usage(sys.argv[0])
    sys.exit(2)
 
for o, a in myopts:
    if o == '-p':
        port = a
    elif o == '-o':
        host = a
    elif o == '-f':
        mfrom = a
    elif o == '-t':
        mto = a
    elif o == '-s':
        msubj = a
    elif o == '-u':
        username = a
    elif o == '-w':
        password = a
    elif o == '-e':
        force_encrypt = True
    elif o == '-q':
        quiet = True
    elif o == '-h':
        usage()
        sys.exit()
#check for minimal required args
if(port == '' or host == '' or mfrom == '' or mto == '' or msubj == ''):
    print("Missing args:")
    usage(sys.argv[0])
    sys.exit(2)
        
#read from stdin for message body if no further args
if(len(args) == 0):
    mbody = sys.stdin.read()
else:
    mbody = args[0]

#generate correct mail date format    
date = datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S%z")
#generate message header and body
msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" \
       % (mfrom, mto, msubj, date, mbody)

#set to 1 for lots of debug messages
debuglevel = 0
#indicate encrypted
encrypted = False

try:
    if(port == '465'):
        smtp = smtplib.SMTP_SSL(host, port)
        encrypted = True
    else:
        smtp = smtplib.SMTP(host, port)
    smtp.set_debuglevel(debuglevel)
    smtp.ehlo()    
    #print(smtp.ehlo_resp)
    if(re.search(r'^STARTTLS\b', smtp.ehlo_resp, re.M | re.I)):
        #print("STARTTLS accepted")
        smtp.starttls()
        smtp.ehlo()
        encrypted = True
        #print(smtp.ehlo_resp)
    #see if login required
    #(order of login & plain not specified and there can be other options)
    if(re.search(r'^AUTH\b.*(?:LOGIN\b.*PLAIN\b|PLAIN\b.*LOGIN\b)', \
                 smtp.ehlo_resp, re.M | re.I)):
        #print("Plain login accepted")
        if(force_encrypt and not encrypted):
            print("Error: Plain text login over non-encrypted connection not allowed.")
            smtp.quit()
            sys.exit(2)
        else:
            smtp.login(username, password)
    #send the mail!
    smtp.sendmail(mfrom, mto, msg)
    smtp.quit()
    if(not quiet):
        print("Mail sent successfully.")
except Exception, e:
    print("Error: unable to send mail.")
    print(str(e))
    sys.exit(2)
#end

That’s all there is to it. The response from smtp.ehlo() is tested to see if STARTTLS is supported; if it is then encryption is kicked in before doing a plain text login provided AUTH PLAIN LOGIN is supported. If the port is 465 then the sequence is started in SSL mode instead by using smtplib.SMTP_SSL.

Usage looks like:

./send-smtp-mail.py -p 25 -o smtp.mail.host \
-f "A User <a.user@me.co.uk>" \
-t "me@me.co.uk" \
-s "Py Test Mail" \
"Test Message Here..."

Pretty simple really and I was pleased with how well the result worked. Python didn’t take much brainpower to get the basics going, at least at the noddy level needed to make something like this work.

It helped to be familiar with the workings of mailservers though. This is the legacy of getting my own home mailserver working using Dovecot & Postfix and having to get relaying through a port 465 tunnel to Virgin Media’s servers working when Postfix doesn’t support port 4652.

Simple Python SMTP mail script – Part 1

TSOHostI host this blog via TSOHost in the UK. On the surface they look like just another low-cost hosting provider, but under the covers they are actually quite programmer-friendly. My cheapo hosting plan has SSH access, cron jobs, up-to-date PHP7 and Mysql and surprisingly good tech support. All good so far and way in excess of the average WordPress user’s needs.

However, I’m not an average (or sensible) WordPress user, so when I started actually writing blog posts I also started thinking about off-site backups and it was far too easy and boring just to install a backup plugin and call it done. As a minor part of my day job I am involved in using Amazon web services, mostly S3 for cloud storage so I started musing about sticking my blog backups in a S3 bucket.

I already had a bash script to create a db backup, tar the wp-content dir, gzip it all up and send it to S3, so stick it on a cron job and pretty much job done eh? … Not quite; I like to get a daily email reminder that the backup ran and some info about the result.

…and that is when things started to unravel…

Shared hosting is cheap, but it comes with quite a few limitations compared to non-shared alternatives3. The main problem is that shared hosting is a magnet for every spammer and scammer around, so the hosting providers have to severely limit access to normal utilities like command-line mail and sendmail. Even when these utilities are there and work, the outgoing mail host is probably on every blacklist around so your mail is going nowhere. This is true for TSOHost too; I couldn’t use any of the easy ways of sending mail from my backup script.

However, all is not lost, they provide an internal SMTP server inside their cloud server farm that can send mails and I already know that works fine as I have used it from one of my PHP applications. Problem solved?

Not quite. Another issue with shared hosting appears – a lot of expected utilities are missing from the command line (ssmtp? – nope) and what is there is old. Like – really old4. They can’t be updated and nothing can be installed except user-land programs.

So how about doing something is a scripting language? TSOHost runs the latest PHP and I could pull down PHPMailer and whip a script up pretty quickly; I’m pretty good with PHP, so that would be easy. It just feels messy though – I started with a single bash script and I’d end up bolting another script and external libraries on the side just to send an email.

How about Perl? Version 5.8.85 is installed and it has the Net::SMTP module. It doesn’t have the Net::SMTP::SSL module though, but that is ok as the TSOHost internal SMTP server doesn’t need encrypted connections. Yes?

No. I don’t really know Perl beyond doing superficial one-liners in shell scripts; plus there is something …painful about Perl6. I’ll consider that as a last resort.

How about Python? Version 2.4.37 is there, the smtplib module loads. Again, SSL won’t work as that didn’t come in until v2.6, but not a problem here. Yes?

Yes! I don’t know Python well either, but that’s what Skynet-Beta Google is for eh?

To be continued…