Last active
October 18, 2019 03:46
-
-
Save joshschmelzle/6181e078bcfd265ea27bee3013e997df to your computer and use it in GitHub Desktop.
python send electronic mail example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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