Sprache auswählen

E-Mails will man eigentlich ständig versenden. Und mit Pyhton geht das sogar noch automatisiert. Damit kann man ganz einfach Statusmeldungen, Zusammenfassungen und sonstige wichtige Meldungen in die Welt verschicken.

MIME Text Message mit Anhang verschicken

Wie bei allem gibt es auch hier verschiedene Wege ans Ziel zu kommen. Um flexibel zu bleiben, benutzen wir hier eine MIME Nachricht. Damit lassen sich Anhänge aller Art etc. mitversenden. Im Beispiel benutze ich einen Gmail Account. Für andere Accounts müssen ggf. die Informationen der SMTP Server angepasst werden.

Text und Anhänge versenden

Die Funktion send_email kann, je nach übergebenen Parametern einfache Textmails oder, wenn der Parameter attachment übergeben wird, auch Mails mit Anhängen versenden.

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':Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.'
        '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äDiese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.','empfäDiese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.']
    login={'user':Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.', 'password':'my_password', 'subject':'my_subject', 'text':'Test Email', 'attachment':'test.txt'}
    send_email(*recipient, **login)

Das Codebeispiel kann einfach ausgeführt werden. Man muss nur in den letzten Zeilen den Empfänger, sowie die Log In Daten für seinen Gmail Account eintragen.

Damit es fehlerfrei durchläuft, muss entweder der Parameter attachment entfernt werden oder es muss im Verzeichnis des Skript einen Datei mit dem Namen test.txt vorhanden sein, die an die Mail angehängt werden kann.

Im Posteingang sieht das dann bspw. so aus. Text und Anhang sind angekommen.

 

HTML Nachrichten

Textnachrichten sind eine feine Sache. Aber was die Formatierung angeht, lassen Sie zu wünschen übrig. Wer Text in verschiedenen Farben, kursiv oder durch Tabellen gegliedert haben möchte, kommt nicht drum herum seine Mails im HTML Format zu verschicken. Dazu müssen wir den Code von oben nur um ein paar einfache Zeilen erweitern. 

Zum Üben verschicken wir eine Tabelle, in der roter und fetter Text, sowie hochgestellte Zeichen enthalten sind.

in HTML sieht das so aus

<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>

Die neue Funktion nenne ich send_html_email.

Den HTML string übergeben ich der Einfachheit halber wirklich als string. Wer große HTML Nachrichten verschicken möchte, sollte sich an dieser Stelle überlegen, ob nicht lieber den Pfad zur HTML Datei übergeben und den String aus dieser einlesen.

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':Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.'
        '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äDiese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.','empfäDiese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.']
    login={'user':Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.', 'password':'my_password', 'subject':'my_subject', 'html':html_string, 'text':'Test Email', 'attachment':'test.txt'}
    send_html_email(*recipient, **login)

Auch das führt im Posteingang zum gewünschten Ergebnis.

 

Bilder einfügen

Die Große Kunst sind zum Schluss Bilder. Das Problem hierbei ist, dass jede App, Outlook, die Browseransichten und Desktop Apps, unterschiedlich mit Bildern umgehen. Manche Varianten werden gnadenlos gefiltert, andere funktionieren nur bei einigen Anbietern und wieder andere verlängern die Ladezeiten.

Eingebettete base64 kodierte Bilder

Zunächst einmal die Version, die zwar unschlagbar schnell im Laden und darstellen ist, aber leider in fast allen Diensten blockiert wird. Nehmen wir mal diesen kleinen Smiley hier. Den habe ich als .png Datei auf der Festplatte. 


Mittels online Konverter oder dem passenden Grafikprogramm kann man daraus eine base64 kodierte Zeichenfolge machen.

Bettet man diese nun in einen passenden Tag einer HTML Seite ein, hat man ein direkt eingebundenes Bild in seiner Mail. Der HTML Tag ist so aufgebaut

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

Leider funktioniert diese Art ein Bild einzubinden meinen Versuchen nach derzeit eigentlich nur noch auf Apple Geräten. Daher ist der Vorteil des schnellen Ladens eigentlich wertlos.

Angehängte Bilder einbetten

Eine Variante ist es, das Bild zunächst an die Mail anzuhängen und dann im HTML Tag als Quelle auszuweisen. Der HTML Tag ist denkbar einfach

<img src="cid:my_image">

Dazu kann man das Skript erweitern

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':Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.'
        '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äDiese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.','empfäDiese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.']
    login={'user':Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.', '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)

Externe Bilder einbinden

Die letzte Möglichkeit ist es, Bilder von online Speicherplätzen zu laden und per passendem Tag im HTML String zu referenzieren

Dazu muss man lediglich in seinem HTML String das Bild folgendermaßen angeben:

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

Auch hier wird man bei verschiedenen Diensten auf Probleme stoßen, denn externer Inhalt wird nicht immer sofort geladen, sondern nur auf Nachfrage oder gar nicht.

Zusammenfassung

Bilder sind und bleiben also ein schwieriges Thema, bei dem es kein richtig oder falsch gibt. Man muss testen, welche Variante für den eigenen Anwendungsfall / Zielgruppe am besten funktioniert. Dabei darf man auch nicht aus dem Auge verlieren, auf welchen Endgeräten die Mails betrachtet werden. mobile Apps zeigen Mail häufig anderst an, als Desktop Apps oder Browser.