-
-
Save BenjaminHoegh/8df744059f9ebeef9beb0534c9c50fa6 to your computer and use it in GitHub Desktop.
Convert HTML emails with python
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
from bs4 import BeautifulSoup | |
import os | |
import re | |
import requests | |
import urlparse | |
import smtplib | |
from smtplib import SMTP | |
from smtplib import SMTP_SSL | |
from smtplib import SMTPAuthenticationError | |
from smtplib import SMTPException | |
from email.mime.multipart import MIMEMultipart | |
from email.mime.text import MIMEText | |
from email.MIMEImage import MIMEImage | |
from jinja2 import Template | |
class EmailSender(object): | |
@classmethod | |
def send_email_message(cls, recipient_email, from_email, from_name, subject_template, message_template, message_context, default_site_url, smtp_settings, verify_ssl=True): | |
message_context['recipient_email'] = recipient_email | |
# -- Render the Subject of the Email | |
subject_template = Template(subject_template) | |
subject = subject_template.render(message_context) | |
subject = ' '.join(subject.split()) # Remove whitespace, newlines, etc | |
# -- Render the Body of the Email | |
message_template = Template(message_template) | |
message_html = message_template.render(message_context) | |
message_no_tags = EmailSender.prepare_body_html(message_html) | |
# -- Send Message | |
EmailSender.send_rendered_message( | |
recipient_email, | |
subject, | |
message_no_tags, | |
unicode(message_html), | |
from_email, | |
from_name, | |
default_site_url, | |
smtp_settings, | |
verify_ssl | |
) | |
@classmethod | |
def send_rendered_message(cls, recipient_email, subject, message_no_tags, message_html, from_email, from_name, default_site_url, smtp_settings, verify_ssl=True): | |
email_headers = {} | |
# -- Create Message | |
msg = EmailSender.create_email( | |
EmailSender.get_formatted_recipient(recipient_email), | |
EmailSender.get_formatted_sender(from_email, from_name), | |
subject, | |
message_no_tags, | |
message_html, | |
default_site_url, | |
verify_ssl | |
) | |
# -- Send Message | |
try: | |
if smtp_settings['smtp_ssl']: | |
if smtp_settings['smtp_port']: | |
smtp = SMTP_SSL( | |
smtp_settings['smtp_host'], smtp_settings['smtp_port']) | |
else: | |
smtp = SMTP_SSL(smtp_settings['smtp_host']) | |
else: | |
if smtp_settings['smtp_port']: | |
smtp = SMTP(smtp_settings['smtp_host'], | |
smtp_settings['smtp_port']) | |
else: | |
smtp = SMTP(smtp_settings['smtp_host']) | |
smtp.ehlo() | |
if smtp.has_extn('STARTTLS'): | |
smtp.starttls() | |
smtp.login(smtp_settings['smtp_user'], | |
smtp_settings['smtp_password']) | |
except (SMTPException, error) as e: | |
raise EAException("Error connecting to SMTP host: %s" % (e)) | |
except SMTPAuthenticationError as e: | |
raise EAException("SMTP username/password rejected: %s" % (e)) | |
smtp.sendmail(from_email, recipient_email, msg.as_string()) | |
smtp.close() | |
@classmethod | |
def get_formatted_sender(cls, from_email, from_name): | |
return "%s <%s>" % (from_name, from_email) | |
@classmethod | |
def get_formatted_recipient(cls, recipient_email): | |
"""Ensure Email Address is Returned in a List""" | |
if isinstance(recipient_email, str): | |
recipient_email = [recipient_email] | |
elif isinstance(recipient_email, unicode): | |
recipient_email = [recipient_email] | |
return ",".join(recipient_email) | |
@classmethod | |
def prepare_body_html(cls, body_html): | |
"""Strips HTML from Email for Text Only""" | |
p = re.compile(r'<.*?>') | |
return p.sub('', body_html) | |
@classmethod | |
def create_email(cls, recipient_email, from_email, subject, message, message_html, default_site_url, verify_ssl=True): | |
# Create the root message and fill in the from, to, and subject headers | |
msg = MIMEMultipart('related') | |
msg['Subject'] = subject | |
msg['From'] = from_email | |
msg['To'] = recipient_email | |
msg.preamble = 'This is a multi-part message in MIME format.' | |
# Encapsulate the plain and HTML versions of the message body in an | |
# 'alternative' part, so message agents can decide which they want to display. | |
msgAlternative = MIMEMultipart('alternative') | |
msg.attach(msgAlternative) | |
msgText = MIMEText(message) | |
msgAlternative.attach(msgText) | |
# We reference the image in the IMG SRC attribute by the ID we give it | |
# below | |
# -- Replace images with CID paths | |
message_with_images_prepared, cid_images = EmailSender.replace_images_with_cid_paths( | |
message_html) | |
# -- Attach CID images | |
EmailSender.attach_cid_images( | |
msg, cid_images, default_site_url, verify_ssl) | |
# -- Attach HTML Message | |
msgText = MIMEText(message_with_images_prepared, | |
"html") | |
msgAlternative.attach(msgText) | |
return msg | |
@classmethod | |
def replace_images_with_cid_paths(cls, body_html): | |
"""Parse the message HTML and identify images""" | |
if body_html: | |
email = BeautifulSoup(body_html, "html5lib") | |
image_counter = 1 | |
cid_images = [] | |
for image in email.findAll('img'): | |
cid_id = "image_%s" % (image_counter) | |
image_counter = image_counter + 1 | |
original_image_src = image['src'] | |
image['src'] = "cid:%s" % (cid_id) | |
cid_images.append({ | |
'src': original_image_src, | |
'cid_id': cid_id | |
}) | |
return (email.prettify(), cid_images) | |
else: | |
return (body_html, []) | |
@classmethod | |
def attach_cid_images(cls, msg, cid_images, default_site_url, verify_ssl=True): | |
"""Attach MIME / CID Images to email""" | |
if cid_images and len(cid_images) > 0: | |
msg.mixed_subtype = 'related' | |
for image in cid_images: | |
try: | |
mime_image = EmailSender.convert_image_to_cid( | |
image['src'], image['cid_id'], default_site_url, verify_ssl) | |
if mime_image: | |
msg.attach(mime_image) | |
except Exception, e: | |
print u"ERROR attacing CID image %s[%s] %s" % (image['cid_id'], image['src'], str(e)) | |
@classmethod | |
def convert_image_to_cid(cls, image_src, cid_id, default_site_url, verify_ssl=True): | |
"""Turn image path into a MIMEImage""" | |
try: | |
if 'data:image/png;base64,' in image_src.lower(): | |
mime_image = MIMEImage(image_src, _subtype="png") | |
else: | |
image_src = EmailSender.normalize_image_url( | |
image_src, default_site_url) | |
path = urlparse.urlparse(image_src).path | |
guess_subtype = os.path.splitext(path)[1][1:] | |
response = requests.get(image_src, verify=verify_ssl) | |
mime_image = MIMEImage( | |
response.content, _subtype=guess_subtype) | |
# Define the image's ID as referenced above | |
mime_image.add_header('Content-ID', '<%s>' % (cid_id)) | |
return mime_image | |
except Exception, e: | |
print u"ERROR creating mime_image %s[%s] %s" % (cid_id, image_src, str(e)) | |
return None | |
@classmethod | |
def normalize_image_url(cls, url, default_site_url): | |
if '//' not in url.lower(): | |
url = u"%s%s" % (default_site_url, url) | |
return url |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment