Select your language

Sending e-mails from a program to inform a user about staus changes or to give them weekly updates is a common task. Python comes with a good package that enables you to do the wigh minumum effort.

MIME messages with attachments

As usual there are many ways how the same result can be achieved. In this example will we send a MIME message which is pretty flexible to use. You can attach all sorts of data.

The example is built upon using a gmail account. You might have to modify the SMTP server infos e.g. if you are using another account.

Sending text and attachments

With the function send_email can you send simple mails containing plain text. If you use the parameters attachment can you add an attachmet as well.

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders

def send_email(*args, **kwargs):
    '''send an email
    
    Parameters
    *args : [recipient1, recipient2, ...]
    is a string containing the recipients separated by commas

    **kwargs : {param1 : val1, param2 : val2, ...}
        'user':This email address is being protected from spambots. You need JavaScript enabled to view it.'
        'password':'my_password'
        'subject':'what is the mail about'
        'text':'text to be sent in the body of the mail'
        'attachment':'path to file'}

    sends a mulitpart email.
    text : will be sent as plain text
    attachment : if set the file will be attached to the email
    '''
    
    #Start the multipart message and fill out the headers and the text
    msg = MIMEMultipart()
    msg['From'] = kwargs['user']
    msg['To'] = ','.join(args)
    msg['Subject'] = kwargs['subject']

    #Add the text to the body of the mail
    msg.attach(MIMEText(kwargs['text'],'plain'))

    #attach file if attachment has been defined
    if 'attachment' in kwargs:
        with open(kwargs['attachment'], 'rb') as f:
            part = MIMEBase('application','octet-stream')
            part.set_payload(f.read())
            encoders.encode_base64(part)
            part.add_header('Content-Disposition','attachment', filename = kwargs['attachment'])
            msg.attach(part)
    

    #Log in and send the mail
    server = smtplib.SMTP('smtp.gmail.com',587)
    server.starttls()
    server.login(kwargs['user'],kwargs['password'])
    server.sendmail(kwargs['user'],args,msg.as_string())
    server.quit()

if __name__ == '__main__':

    #simple test. send a mail with an attachment
    recipient=['empfäThis email address is being protected from spambots. You need JavaScript enabled to view it.','empfäThis email address is being protected from spambots. You need JavaScript enabled to view it.']
    login={'user':This email address is being protected from spambots. You need JavaScript enabled to view it.', 'password':'my_password', 'subject':'my_subject', 'text':'Test Email', 'attachment':'test.txt'}
    send_email(*recipient, **login)

Just test the example by modifying the recipients and the login data. Make sure you have a file test.txt in the same folder as the script. Otherwise remove the attachment entry from the login variable.

The result should look somewhat like the picture below. A short mail with the file test.txt attached.

 

HTML messages

Plain text is nice and simple. But when it comes to fancy formatting that highlights important information and is just visually nice to the recipient, they have a lot of limitations. 

Well then, HTML comes to the rescue. HTML will let you format you mail with a lot of nice features, like tables, colors, many fonts etc. 

The example I am showing here will send a table containing three cells. Each cell has a different format.

This here is the HTML representation of the table. Either you know how to write HTML or you use one of the many WYSIWYG editors that are available on the internet.

<table border="1">
<tbody>
<tr>
<td><span style="color: #ff0000;">Das ist ein Text in roter Farbe</span></td>
</tr>
<tr>
<td><strong>Das ist noch ein Text</strong></td>
</tr>
<tr>
<td><em>f(x)=y<sup>3</sup></em></td>
</tr>
</tbody>
</table>

The new function will be called send_html_email.

The example will hand the HTML as a string. If yo have larger mails to send you should consider to store the HTML in a file and read it from there.

def send_html_email(*args, **kwargs):
    '''send an email
    
    Parameters
    *args : [recipient1, recipient2, ...]
    is a string containing the recipients separated by commas

    **kwargs : {param1 : val1, param2 : val2, ...}
        'user':This email address is being protected from spambots. You need JavaScript enabled to view it.'
        'password':'my_password'
        'subject':'what is the mail about'
        'html': 'HTML file as string'
        'text': optional / 'text to be displayed if reveiver cannot use html'
        'attachment':'path to file'}

    sends a mulitpart email.
    text : will be sent as plain text
    attachment : if set the file will be attached to the email
    '''
    
    #Start the multipart message and fill out the headers and the text
    msg = MIMEMultipart()
    msg['From'] = kwargs['user']
    msg['To'] = ','.join(args)
    msg['Subject'] = kwargs['subject']

    #Add the text to the body of the mail if specified.
    #html will be added as second part
    if 'text' in kwargs:
        msg.attach(MIMEText(kwargs['text'],'plain'))
    msg.attach(MIMEText(kwargs['html'],'html'))

    #attach file if attachment has been defined
    if 'attachment' in kwargs:
        with open(kwargs['attachment'], 'rb') as f:
            part = MIMEBase('application','octet-stream')
            part.set_payload(f.read())
            encoders.encode_base64(part)
            part.add_header('Content-Disposition','attachment', filename = kwargs['attachment'])
            msg.attach(part)
    

    #Log in and send the mail
    server = smtplib.SMTP('smtp.gmail.com',587)
    server.starttls()
    server.login(kwargs['user'],kwargs['password'])
    server.sendmail(kwargs['user'],args,msg.as_string())
    server.quit()

if __name__ == '__main__':

    #simple test. send a mail with an attachment
    html_string ='''\
    <table border="1">
    <tbody>
    <tr>
    <td><span style="color: #ff0000;">Das ist ein Text in roter Farbe</span></td>
    </tr>
    <tr>
    <td><strong>Das ist noch ein Text</strong></td>
    </tr>
    <tr>
    <td><em>f(x)=y<sup>3</sup></em></td>
    </tr>
    </tbody>
    </table>'''
    
    recipient=['empfäThis email address is being protected from spambots. You need JavaScript enabled to view it.','empfäThis email address is being protected from spambots. You need JavaScript enabled to view it.']
    login={'user':This email address is being protected from spambots. You need JavaScript enabled to view it.', 'password':'my_password', 'subject':'my_subject', 'html':html_string, 'text':'Test Email', 'attachment':'test.txt'}
    send_html_email(*recipient, **login)

Execute the example and the result should be clost to what you see below.

 

Embed images

Finally, the big one. Images are nice in mails and give them a fancy touch. But they have a dark side. Almost every App, Outlook, Browsers and Desktop Apps handle images differently. Some filter them out mercyliessly.

As well different ways of embedding images work only on a selection of apps and some increase the time required to open the mails.

Embedded base64 coded images

This version is fast to load and good to show. But it is blocked in most messengers.

We will send this little smiley here. I have it stored as a .png file on the hard drive. 


You can use an online Konverter or a suitable graphics software to create a base64 coded string from it.

This string is simply embedded in the HTML string of our mail at the spot where it shall be shown. The HTML tag for that looks like this here: 

<img src="data:image/png;base64,Here Comes the base64 coded image">

I tried a little with Apple and Android devies. Seems like only Apple supports this kind of embedded image. If you are not limiting your customer base to Apple that version is not of good use.

Embed attached images

The second way is to attach an image to the mail. Then you reference it as the source of the image in your HTML message.

The HTML tag is pretty simple:

<img src="cid:my_image">

The extended script:

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.utils import make_msgid
from email import encoders

def send_html_email_with_cid(*args, **kwargs):
    '''send an email
    
    Parameters
    *args : [recipient1, recipient2, ...]
    is a string containing the recipients separated by commas

    **kwargs : {param1 : val1, param2 : val2, ...}
        'user':This email address is being protected from spambots. You need JavaScript enabled to view it.'
        'password':'my_password'
        'subject':'what is the mail about'
        'html': 'HTML file as string'
        'embedded': 'path to embedded file' needs to be referenced in HTML img tag with cid_1
        'text': optional / 'text to be displayed if reveiver cannot use html'
        'attachment':'path to file'}
        'embedded': path to file which is emedded in cid tag in html

    sends a mulitpart email.
    text : will be sent as plain text
    attachment : if set the file will be attached to the email
    '''
    
    #Start the multipart message and fill out the headers and the text
    msg = MIMEMultipart()
    msg['From'] = kwargs['user']
    msg['To'] = ','.join(args)
    msg['Subject'] = kwargs['subject']

    #Add the text to the body of the mail if specified.
    #html will be added as second part
    if 'text' in kwargs:
        msg.attach(MIMEText(kwargs['text'],'plain'))
    msg.attach(MIMEText(kwargs['html'],'html'))   

    if 'embedded' in kwargs:
        with open(kwargs['embedded'],'rb') as img:
            cid_image = MIMEImage(img.read())
            cid_image.add_header('Content-ID','cid1.png')
            msg.attach(cid_image)
            
    #attach file if attachment has been defined
    if 'attachment' in kwargs:
        with open(kwargs['attachment'], 'rb') as f:
            part = MIMEBase('application','octet-stream')
            part.set_payload(f.read())
            encoders.encode_base64(part)
            part.add_header('Content-Disposition','attachment', filename = kwargs['attachment'])
            msg.attach(part)
    

    #Log in and send the mail
    server = smtplib.SMTP('smtp.gmail.com',587)
    server.starttls()
    server.login(kwargs['user'],kwargs['password'])
    server.sendmail(kwargs['user'],args,msg.as_string())
    server.quit()

if __name__ == '__main__':

    #simple test. send a mail with an attachment   
    recipient=['empfäThis email address is being protected from spambots. You need JavaScript enabled to view it.','empfäThis email address is being protected from spambots. You need JavaScript enabled to view it.']
    login={'user':This email address is being protected from spambots. You need JavaScript enabled to view it.', 'password':'my_password', 'subject':'my_subject', 'text':'Test Email', 'attachment':'test.txt'}

    html_cid = '<html><head></head><body><table border="1"><img src="cid:cid1.png"></img></table></body></html>'
    login['html'] = html_cid
    login['embedded'] = 'smile.png'
    send_html_email_with_cid(*recipient, **login)

Embed external images

The last possibility is to use images which are not stored in the mail but at some location in the internet. Maybe a googel drive or any other homepage. You can reference this again in your HTML message.

The image tag has to be modified like this:

<img src="http://www.link_zum_bild.de">

Again, you will encounter some problems with various e-mail providers. Some will block the external images others will ask the recipient if he trusts the source. 

Conclusion

Images are and will remain tricy. There is no right or wrong. You will have to test a lot which version is working for your specific application or user group. One needs to think about possible devices, browsers etc. which your users are going to work with.