Nachdem ich zuhause das DMS-System aufgesetzt und meine ganzen Dokumente digitalisiert habe stand das Thema der PDF-Dokumente welche per Mail reinkommen auf dem Plan. Ein Script welches mir E-Mail PDF-Anhang speichern kann musst her.
Archivierung von Rechnungen aus E-Mails
Egal wo man heute etwas bestellt, meist kommen die Rechnungen in elektronischer Form. Doch wie kann ich diese Rechnungen nun am Einfachsten in meinem DMS-System archivieren? Eigentlich ging ich davon aus dass ein System wie z.B. ecoDMS ein solches E-Mail Gateway automatisiert mitliefert, leider war dies jedoch nicht der Fall.
Somit musste eine eigene Lösung her. Kein Problem, ein kleines Script sollte das Problem schnell Lösen. Die Grundidee: Ich erzeuge mir ein Mailpostfach an welches ich alle E-Mails, deren PDF-Anhänge archiviert werden sollen weiterleite. Dieses Postfach wird von einem Cronjob abgeholt, die PDF-Anhänge aus den Mails heruntergeladen und in den Übergabeordner von ecoDMS gelegt.
Nach einigen Versuchen ist das folgende Python-Script entstanden welches bei mir nun schon seit über 2 Jahren produktiv im Einsatz ist und stabil seinen Dienst verrichtet.
E-Mail PDF-Anhang speichern – Python-Script
#!/usr/bin/env python
"""
importDmsMails.py
Check emails at :const:`PROVIDER` for attachments and save them to
:const:`SAVEDIR`.
"""
from __future__ import absolute_import, division, print_function
import email
import os
import poplib
PROVIDER = 'XXXX'
USER = 'XXXX'
PASSWORD = 'XXXX'
SAVE_DIR = '/opt/ecodms/workdir/scaninput/'
def getNotExistingFileName(saveDir, srcFileName):
counter = 1
if (srcFileName.endswith("pdf?=")):
srcFileName = "scanbymail.pdf"
while (True):
currentName = srcFileName
if (counter > 1):
currentName = str(counter) + "_" + srcFileName
if (os.path.isfile(saveDir + currentName)):
counter = counter + 1
else:
return currentName
def save_attachments(mail_string):
attachments = list()
for part in email.message_from_string(mail_string).walk():
name = part.get_filename()
if name:
print('Handle Attachment {0!r}.'.format(name))
if name.lower().endswith(("pdf", "pdf?=")):
data = part.get_payload(decode=True)
name = name.replace("/", "_")
targetFileName = getNotExistingFileName(SAVE_DIR, name)
f = file(os.path.join(SAVE_DIR, targetFileName), 'wb')
f.write(data)
f.close()
print('Found and saved attachment {0!r}.'.format(targetFileName))
def main():
try:
client = poplib.POP3_SSL(PROVIDER)
client.user(USER)
client.pass_(PASSWORD)
message_numbers = (int(s.split()[0]) for s in client.list()[1])
for message_number in message_numbers:
save_attachments('\n'.join(client.retr(message_number)[1]))
client.dele(message_number)
finally:
client.quit()
if __name__ == '__main__':
main()
Was genau tut das Script?
Das hier aufgeführte Python-Script wird bei mir regelmäßig durch einen Cronjob ausgeführt. Das Script verbindet sich zu meinem Mailserver und lädt sich alle Mails herunter. Hängt an einer Mail ein PDF-Anhang so wird dieser Anhang im Eingangsverzeichnis von ecoDMS gesichert. Bei der Speicherung der Datei prüft das Script ob hier bereits eine Datei mit gleichem Namen vorliegt. Ist dies der Fall so wird der Dateiname durch einen Zähler erweitert so dass keine bereits existierende Datei überschrieben wird.
Wer möchte kann das Script natürlich gerne verwenden und auf seine Bedürfnisse anpassen. Natürlich übernehme ich keine Garantie für die Funktion des Scriptes, die Verwendung erfolgt auf eigene Gefahr 🙂
Update – 11. Juni 2021
Ich habe gestern eine Mail erhalten welche mich auf ein Problem hinwies. Thomas erhielt beim Ausführen des Scriptes den folgenden Fehler:
python3 importDmsMails.py
Traceback (most recent call last):
File "importDmsMails.py", line 61, in <module>
main()
File "importDmsMails.py", line 54, in main
save_attachments('\n'.join(client.retr(message_number)[1]))
TypeError: sequence item 0: expected str instance, bytes found
Bei der Analyse der Meldung stellte sich heraus, dass ich eine andere Python-Version als Thomas verwende. Bei mir läuft Python in der Version 2.7.10, bei ihm auf Python 3.
Ich habe für dieses Problem eine neue Version des Scriptes bereit gestellt. Mehr dazu findet Ihr hier:
E-Mail Anhänge nach ecoDMS importieren – mit Python 3
Wäre Super, wenn dies unter python 3 Anwendung finden könnte.
Danke für den Hinweis, ich habe hierfür ein Update bereit gestellt
Du findest das neues Script hier: https://www.schiffler.eu/e-mail-anhaenge-nach-ecodms-importieren-mit-python-3/
Hallo Thomas,
da ich ebenfalls die Python-Version 3.7.3 nutze stieß ich auf das Problem, welches in Ihrem Artikel unter “Update – 11. Juni 2021” hingewiesen wurde.
Füge ich wie in Ihrem Artikel beschrieben die Option “b” in die Befehlszeile “save_attachments(b’ …” hinzu erscheint folgende Fehlermeldung:
$ python mail_anhang_dms.py
Traceback (most recent call last):
File “mail_anhang_dms.py”, line 58, in
main()
File “mail_anhang_dms.py”, line 52, in main
save_attachments(b’\n’.join(client.retr(message_number)[1]))
File “mail_anhang_dms.py”, line 33, in save_attachments
for part in email.message_from_string(mail_string).walk():
File “/usr/lib/python3.7/email/__init__.py”, line 38, in message_from_string
return Parser(*args, **kws).parsestr(s)
File “/usr/lib/python3.7/email/parser.py”, line 68, in parsestr
return self.parse(StringIO(text), headersonly=headersonly)
TypeError: initial_value must be str or None, not bytes
Gibt es hierfür eine Lösung?
Viele Grüße aus der sächsischen Schweiz und ich wünsche ein schönes Wochenende
Rainer Pfau
Ja, es gibt eine Lösung 🙂
Ich habe mich da mal hingesetzt und ein Update gebaut – mehr dazu hier: https://www.schiffler.eu/e-mail-anhaenge-nach-ecodms-importieren-mit-python-3/
Guten Tag Herr Schiffler,
Ihr Python-Script um PDF-Anhänge aus eMails zu extrahieren finde ich sehr praktisch und spannend.
Jedoch bekam ich als Python-Anfänger Schwierigkeiten die Einrückungen richtig zu setzen, um Codeblöcke zu bestimmen.
Damit verbrachte ich mehrere Stunden, natürlich machte ich mir anhand des Programmablaufes Gedanken, wo beginnt und endet ein Codeblock und an welcher Stelle ist dieser vermutlich verschachtelt. Leider hatte ich keinen Erfolg und konnte Ihr Script nicht nutzen.
Wäre es Ihnen möglich Ihr Python-Script mit Einrückungen abzubilden, um zum einen, andere Python-Anfänger nicht gleich zu entmutigen, wenn diese ebenso auf dieses Problem stoßen und zum Anderen zu sehen, welche Fehler ich bei der Umsetzung der korrekten Einrückungen gemacht habe.
Viele Grüße aus der sächsischen Schweiz und bleiben Sie gesund
Rainer Pfau
Hallo Rainer,
danke für den Hinweis. In der Tat ist das mit dem Lesen ein wenig schwer wenn die nötigen CSS-Files nicht richtig geladen sind. Ich schau mir das mal an und überlege mir was
Gruß Thomas