Skip to content

Instantly share code, notes, and snippets.

@dwurf
Created December 15, 2019 22:44
Show Gist options
  • Save dwurf/495fc88d621e096c8f667576eeea2a17 to your computer and use it in GitHub Desktop.
Save dwurf/495fc88d621e096c8f667576eeea2a17 to your computer and use it in GitHub Desktop.
Using Python's built-in smtplib to send files
#!/usr/bin/env python
"""Script for sending an email (with an optional file attachment)."""
# Modified from the documentation at
# https://docs.python.org/2/library/email-examples.html
#
# Tested against python 2.6, 2.7 and 3.6
#
# For help, run this script!
import os
import sys
import smtplib
# For guessing MIME type based on file name extension
import mimetypes
import ssl
from optparse import OptionParser
from email import encoders
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
COMMASPACE = ', '
def main(args=sys.argv):
parser = OptionParser(usage="""\
Send an email. Optionally, include a file attachment. The body will be
read from standard input, type <Enter>.<Enter> to end input.
Usage: %prog [options]
The email is sent by forwarding to your local SMTP server (running on
localhost), unless you specify either the -t or the -o option.
""")
parser.add_option('-s', '--sender',
type='string', action='store', metavar='SENDER',
help='The value of the From: header (required)')
parser.add_option('-r', '--recipient',
type='string', action='append', metavar='RECIPIENT',
default=[], dest='recipients',
help='A To: header value (at least one required)')
parser.add_option('-u', '--subject',
type='string', action='store', metavar='SUBJECT',
help='The subject of the email. (required)')
parser.add_option('-f', '--file',
type='string', action='store', metavar='INFILE',
help="""Send the specified file as an attachment.
(optional)""")
parser.add_option('-t', '--smtp-host',
type='string', action='store', metavar='HOST',
help="""Use this smtp server to send mail, instead
of localhost. (optional)""")
parser.add_option('-l', '--login-name',
type='string', action='store', metavar='USER',
help="""Username to authenticate to the SMTP
server. (optional)""")
parser.add_option('-p', '--password',
type='string', action='store', metavar='PASSWORD',
help="""Password to authenticate to the SMTP
server. (optional)""")
parser.add_option('--tls', default=False, action='store_true',
metavar='TLS', help="""Use TLS for connection""")
parser.add_option('-o', '--output',
type='string', action='store', metavar='OUTFILE',
help="""Print the composed message to FILE instead of
sending the message to the SMTP server. (for
testing - optional)""")
opts, args = parser.parse_args(args)
if not opts.sender or not opts.recipients or not opts.subject:
parser.print_help()
sys.exit(1)
msg = None
if opts.file and os.path.isfile(opts.file):
print("Adding file " + opts.file)
# Guess the content type based on the file's extension. Encoding
# will be ignored, although we should check for simple things like
# gzip'd or compressed files.
ctype, encoding = mimetypes.guess_type(opts.file)
if ctype is None or encoding is not None:
# No guess could be made, or the file is encoded (compressed), so
# use a generic bag-of-bits type.
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
if maintype == 'text':
fp = open(opts.file)
# Note: we should handle calculating the charset
msg = MIMEText(fp.read(), _subtype=subtype)
fp.close()
elif maintype == 'image':
fp = open(opts.file, 'rb')
msg = MIMEImage(fp.read(), _subtype=subtype)
fp.close()
elif maintype == 'audio':
fp = open(opts.file, 'rb')
msg = MIMEAudio(fp.read(), _subtype=subtype)
fp.close()
else:
fp = open(opts.file, 'rb')
msg = MIMEBase(maintype, subtype)
msg.set_payload(fp.read())
fp.close()
# Encode the payload using Base64
encoders.encode_base64(msg)
# Set the filename parameter
# Use only the base filename, not the whole path
fileparts = str(opts.file).split("/")
filebase = fileparts[len(fileparts) - 1]
msg.add_header('Content-Disposition', 'attachment', filename=filebase)
# Get input from stdin
lines = ""
try:
while 1:
line = sys.stdin.readline()
if not line or line == ".\n":
break
lines += line
except (IOError, OSError):
pass
# Create the enclosing (outer) message
outer = MIMEMultipart('mixed')
outer['Subject'] = opts.subject
outer['To'] = COMMASPACE.join(opts.recipients)
outer['From'] = opts.sender
# Add body
if lines:
body = MIMEText(lines, 'plain')
outer.attach(body)
else:
print("Sending without a message body...\n")
# Add attachment
if msg is not None:
outer.attach(msg)
# Now send or store the message
composed = outer.as_string()
if opts.output:
fp = open(opts.output, 'w')
fp.write(composed)
fp.close()
else:
host = opts.smtp_host if opts.smtp_host else 'localhost'
# Create smtp object
if opts.tls:
s = smtplib.SMTP_SSL(host, timeout=5)
else:
s = smtplib.SMTP(host, timeout=5)
# Login
if opts.login_name and opts.password:
s.login(opts.login_name, opts.password)
# Send, then quit
s.sendmail(opts.sender, opts.recipients, composed)
s.quit()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment