Skip to content

Instantly share code, notes, and snippets.

@joshschmelzle
Last active October 18, 2019 03:46
Show Gist options
  • Save joshschmelzle/6181e078bcfd265ea27bee3013e997df to your computer and use it in GitHub Desktop.
Save joshschmelzle/6181e078bcfd265ea27bee3013e997df to your computer and use it in GitHub Desktop.
python send electronic mail example
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
jemail.py
~~~~~~~~~
a script for learning about sending email with Python
"""
# standard library imports
import argparse
import inspect
import logging
import logging.config
import smtplib
import socket
import ssl
import sys
import textwrap
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# third party imports
try:
import yaml
except ModuleNotFoundError as error:
if error.name == "yaml":
print("required module yaml not found. please install and run again.")
sys.exit(-1)
def setup_parser() -> argparse:
"""
Build parser for program.
Defines defaults for the config and device list.
:return: argparse object
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=textwrap.dedent(
"""
a test script for sending emails
"""
),
)
parser.add_argument(
"-debugging",
action="store_const",
help="increase output for debugging",
const=logging.DEBUG,
default=logging.INFO,
)
parser.add_argument(
"-config",
type=str,
metavar="<CONFIG>.yml",
default="email.yml",
help="specify path for YAML config",
)
return parser
def setup_logger(args) -> logging.Logger:
"""
Build logger for the program.
:param args:
:return: logging object
"""
default_logging = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"}
},
"handlers": {
"default": {
"level": args.debugging,
"formatter": "standard",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
}
},
"loggers": {"": {"handlers": ["default"], "level": args.debugging}},
}
logging.config.dictConfig(default_logging)
return logging.getLogger(__name__)
def load_config(config_file_path: str) -> dict:
"""Safe load YAML config file."""
log = logging.getLogger(inspect.stack()[0][3])
config = None
try:
with open(config_file_path) as file:
config = yaml.safe_load(file)
except FileNotFoundError as error:
log.exception(f"could not find config file: {error}")
except yaml.YAMLError as error:
log.exception(f"error in {config_file_path}\n{error}\nexiting...")
if config:
return config
else:
sys.exit(-1)
def send_email(
config: dict, subject: str, plaintext="", html="", attachment=""
) -> bool:
"""Send email."""
log = logging.getLogger(inspect.stack()[0][3])
sender = None
context = ssl.create_default_context()
server = config["smtp"]["server"]
port = config["smtp"]["port"]
sender = config["smtp"]["sender"]
recipients = config["smtp"]["recipients"]
password = config["smtp"]["password"]
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = sender
if plaintext:
part1 = MIMEText(plaintext, "plain")
message.attach(part1)
if html:
part2 = MIMEText(html, "html")
message.attach(part2)
if attachment:
try:
with open(attachment, "rb") as _attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(_attachment.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition", f"attachment; filename= {attachment}"
)
message.attach(part)
except FileNotFoundError as error:
log.error(f"could not attach file: {error}")
try:
if port == 587:
context = ssl.create_default_context()
with smtplib.SMTP(server, port) as server:
server.ehlo()
server.starttls(context=context)
server.ehlo()
server.login(sender, password)
server.sendmail(message.get("From"), recipients, message.as_string())
return True
elif port == 465:
with smtplib.SMTP_SSL(server, port, context=context) as server:
server.login(sender, password)
server.sendmail(message.get("From"), recipients, message.as_string())
return True
else:
log.error(f"unknown port {port}")
return False
except smtplib.SMTPException as error:
log.error(f"unable to send email: {error}")
return False
except socket.gaierror as error:
log.error(f"invalid host name: {error}")
return False
def main() -> None:
"""
You will need a config file. This example needs a YAML config file.
smtp:
server: smtp.gmail.com
port: 465|587
sender: youraccount@gmail.com
recipients: [youraccount+eden@gmail.com, youraccount+cornwall@gmail.com]
password: password
"""
parser = setup_parser()
args = parser.parse_args()
log = setup_logger(args)
log.debug(f"{sys.version}")
log.debug(f"args {args}")
config = load_config(args.config)
subject = "Woah! Emails from Snakes!"
plaintext = """
Yo!
Rad content here.
"""
html = """
<html>
<body>
<h1>Yo!</h1>
<p>Rad<br/>Content<br/>Here!<br/></p>
<p>Email sent from <a href="https://python.org">Python</a>!</p>
</body>
</html>
"""
attachment = "attachment.pdf"
result = send_email(
config, subject, plaintext=plaintext, html=html, attachment=attachment
)
if result:
print("mail sent")
else:
print("failed to send mail")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment