mailbox - Manipola Archivi Email

Scopo: Lavora con messaggi email in vari formati di file locali

Il modulo mailbox definisce una API comune per accedere a messaggi email conservati in formati disco locale, inclusi:

  • Maildir
  • mbox
  • MH
  • Babyl
  • MMDF

Ci sono classi base per Mailbox e Message, e ciascun formato di casella di posta include un paio di sottoclassi corrispondenti per implementare i dettagli per quel formato.

mbox

Il formato mbox è quello più semplice da mostrare nella documentazione, visto è interamente in testo semplice. Ogni casella di posta è conservata in un singolo file, con tutti i messaggi concatenati assieme. Ogni volta che viene rilevata una riga che inizia con "From " ("From" seguito da un singolo spazio) essa viene trattata come l'inizio di un nuovo messaggio. Ogni volta che detti caratteri appaiono all'inizio di una riga di corpo del messaggio vengono prefissati dalla sequenza di escape ">".

Creare una Casella di Posta mbox

Si istanzi la classe mbox passando il nome del file al costruttore. Se il file non esiste, viene creato, quando si utilizza add() per accodare i messaggi.

# mailbox_mbox_create.py

import mailbox
import email.utils

from_addr = email.utils.formataddr(('Autore',
                                    'autore@esempio.com'))
to_addr = email.utils.formataddr(('Destinatario',
                                  'destinatario@esempio.com'))

payload = '''Questo e' il corpo.
From (sara' prefissato da sequenza di escape).
Ci sono 3 righe.
'''

mbox = mailbox.mbox('esempio.mbox')
mbox.lock()
try:
    msg = mailbox.mboxMessage()
    msg.set_unixfrom('autore Sat Feb  7 01:05:34 2009')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Messaggio campione 1'
    msg.set_payload(payload)
    mbox.add(msg)
    mbox.flush()

    msg = mailbox.mboxMessage()
    msg.set_unixfrom('autore')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Messaggio campione 2'
    msg.set_payload("Questo e' il secondo corpo.\n")
    mbox.add(msg)
    mbox.flush()
finally:
    mbox.unlock()

print(open('esempio.mbox', 'r').read())

Il risultato dello script è un nuovo file di casella di posta con due messaggi email.

$ python3 mailbox_mbox_create.py

From MAILER-DAEMON Mon Nov 11 08:01:54 2019
From: Autore <autore@esempio.com>
To: Destinatario <destinatario@esempio.com>
Subject: Messaggio campione 1

Questo e' il corpo.
>From (sara' prefissato da sequenza di escape).
Ci sono 3 righe.

From MAILER-DAEMON Mon Nov 11 08:01:54 2019
From: Autore <autore@esempio.com>
To: Destinatario <destinatario@esempio.com>
Subject: Messaggio campione 2

Questo e' il secondo corpo.
Leggere una Casella di Posta mbox

Per leggere una casella di posta esistente, la si apra e si tratti l'oggetto mbox come un dizionario. Le chiavi sono valori arbitrari definiti dall'istanza mailbox e non hanno un particolare significato se non quello di fungere da identificatori interni per gli oggetti messaggio.

# mailbox_mbox_read.py

import mailbox

mbox = mailbox.mbox('esempio.mbox')
for message in mbox:
    print(message['subject'])

La casella di posta aperta supporta il protocollo iteratore, ma al contrario dei veri oggetti dizionario l'iteratore predefinito per la casella di posta lavora sui valori in luogo delle chiavi.

$ python3 mailbox_mbox_read.py

Messaggio campione 1
Messaggio campione 2
Rimuovere Messaggi da una Casella di Posta mbox

Per rimuovere un messaggio esistente da un file mbox, si usi la sua chiave con remove() oppure si usi del.

# mailbox_mbox_remove.py

import mailbox

mbox = mailbox.mbox('esempio.mbox')
mbox.lock()
try:
    to_remove = []
    for key, msg in mbox.iteritems():
        if '2' in msg['subject']:
            print('Rimozione di:', key)
            to_remove.append(key)
    for key in to_remove:
        mbox.remove(key)
finally:
    mbox.flush()
    mbox.close()

print(open('esempio.mbox', 'r').read())

I metodi lock() e unlock() sono utilizzati per prevenire problemi derivanti da accessi simultanei al file, e flush() forza la scrittura su disco delle modifiche.

$ python3 mailbox_mbox_remove.py

Rimozione di: 1
From MAILER-DAEMON Mon Nov 11 08:19:32 2019
From: Autore <autore@esempio.com>
To: Destinatario <destinatario@esempio.com>
Subject: Messaggio campione 1

Questo e' il corpo.
>From (sara' prefissato da sequenza di escape).
Ci sono 3 righe.

Maildir

Il formato Maildir fu creato per eliminare il problema di modifiche concorrenti a un file mbox. Invece che usare un file singolo, la casella di posta è organizzata come directory nella quale ciascun messaggio è contenuto nel suo proprio file. Questo consente anche un annidamento delle caselle di posta, quindi la API per una casella di posta Mailbox è estesa con metodi che lavorano con sottocartelle.

Creare una Casella di Posta Maildir

La sola reale differenza tra la creazione di una Maildir rispetto a una mbox è che l'argomento per il costruttore è un nome di directory invece che un nome di file. Come prima, se la casella di posta non esiste, viene creata quando vengono aggiunti i messaggi.

# mailbox_maildir_create.py

import mailbox
import email.utils
import os

from_addr = email.utils.formataddr(('Autore', 'autore@esempio.com'))
to_addr = email.utils.formataddr(('Destinatario', 'destinatario@esempio.com'))

payload = '''Questo e' il corpo.
From (non viene preceduta da sequenza di escape).
Ci sono tre righe.
'''

mbox = mailbox.Maildir('Esempio')
mbox.lock()
try:
    msg = mailbox.mboxMessage()
    msg.set_unixfrom('autore Sat Feb  7 01:05:34 2009')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Messaggio campione 1'
    msg.set_payload(payload)
    mbox.add(msg)
    mbox.flush()

    msg = mailbox.mboxMessage()
    msg.set_unixfrom('autore Sat Feb  7 01:05:34 2009')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Messaggio campione 2'
    msg.set_payload("Questo e' il secondo corpo.\n")
    mbox.add(msg)
    mbox.flush()
finally:
    mbox.unlock()

for dirname, subdirs, files in os.walk('Esempio'):
    print(dirname)
    print('  Directory:', subdirs)
    for name in files:
        fullname = os.path.join(dirname, name)
        print('\n***', fullname)
        print(open(fullname).read())
        print('*' * 20)

Quando i messaggi sono aggiunti alla casella di posta, vanno nella nuova sottodirectory.

Anche se è sicuro scrivere alla stessa maildir da processi multipli, add() non è sicuro a livello di thread. Si utilizzi un semaforo o altro dispositivo di bloccaggio per prevenire modifiche simultanee alla casella di posta da thread multipli dello stesso processo.
$ python3 mailbox_maildir_create.py

Esempio
  Directory: ['new', 'tmp', 'cur']
Esempio/new
  Directory: []

*** Esempio/new/1573461573.M72514P9228Q1.robby-System-Product-Name
From: Autore <autore@esempio.com>
To: Destinatario <destinatario@esempio.com>
Subject: Messaggio campione 1

Questo e' il corpo.
From (non viene preceduta da sequenza di escape).
Ci sono tre righe.

********************

*** Esempio/new/1573461573.M98691P9228Q2.robby-System-Product-Name
From: Autore <autore@esempio.com>
To: Destinatario <destinatario@esempio.com>
Subject: Messaggio campione 2

Questo e' il secondo corpo.

********************
Esempio/tmp
  Directory: []
Esempio/cur
  Directory: []

Qualora fosse necessario, dopo avere letto i messaggi, è possibile spostarli dalla sottodirectory cur tramite il metodo set_subdir() di MaildirMessage.

# mailbox_maildir_set_subdir.py

import mailbox
import os

print('Prima:')
mbox = mailbox.Maildir('Esempio')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_subdir(),
                                 message['subject']))
        message.set_subdir('cur')
        # Si chiede a mailbox di aggiornare il messaggio
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

print('\nDopo:')
mbox = mailbox.Maildir('Esempio')
for message in mbox:
    print('{:6} "{}"'.format(message.get_subdir(),
                             message['subject']))

print()
for dirname, subdirs, files in os.walk('Esempio'):
    print(dirname)
    print('  Directory:', subdirs)
    for name in files:
        fullname = os.path.join(dirname, name)
        print(fullname)

Anche se una casella maildir già include una directory "tmp", i soli argomenti validi per set_subdir() sono "cur" e "new".

$ python3 mailbox_maildir_set_subdir.py
Prima:
new    "Messaggio campione 1"
new    "Messaggio campione 2"

Dopo:
cur    "Messaggio campione 1"
cur    "Messaggio campione 2"

Esempio
  Directory: ['new', 'tmp', 'cur']
Esempio/new
  Directory: []
Esempio/tmp
  Directory: []
Esempio/cur
  Directory: []
Esempio/cur/1573461573.M72514P9228Q1.robby-System-Product-Name
Esempio/cur/1573461573.M98691P9228Q2.robby-System-Product-Name
Leggere una Casella di Posta Maildir

Il meccanismo di lettura di una Maildir esistente è uguale a quella di una casella mbox.

# mailbox_maildir_read.py

import mailbox

mbox = mailbox.Maildir('Esempio')
for message in mbox:
    print(message['subject'])

Non è garantito che i messaggi vengano letti con un ordine particolare.

$ python3 mailbox_maildir_read.py

Messaggio campione 2
Messaggio campione 1
Rimuovere Messaggi da una Casella di Posta Maildir

Per rimuovere un messaggio esistente da una Maildir, si passi la sua chiave a remove() oppure si usi del.

# mailbox_maildir_remove.py

import mailbox
import os

mbox = mailbox.Maildir('Esempio')
mbox.lock()
try:
    to_remove = []
    for key, msg in mbox.iteritems():
        if '2' in msg['subject']:
            print('Eliminazione di:', key)
            to_remove.append(key)
    for key in to_remove:
        mbox.remove(key)
finally:
    mbox.flush()
    mbox.close()

for dirname, subdirs, files in os.walk('Esempio'):
    print(dirname)
    print('  Directory:', subdirs)
    for name in files:
        fullname = os.path.join(dirname, name)
        print('\n***', fullname)
        print(open(fullname).read())
        print('*' * 20)

Non vi è modo di calcolare la chiave per un messaggio, quindi si usi items() o iteritems() per recuperare contemporaneamente la chiave e l'oggetto messaggio dalla casella di posta.

$ python3 mailbox_maildir_remove.py

Eliminazione di: 1573461573.M98691P9228Q2.robby-System-Product-Name
Esempio
  Directory: ['new', 'tmp', 'cur']
Esempio/new
  Directory: []
Esempio/tmp
  Directory: []
Esempio/cur
  Directory: []

*** Esempio/cur/1573461573.M72514P9228Q1.robby-System-Product-Name
From: Autore <autore@esempio.com>
To: Destinatario <destinatario@esempio.com>
Subject: Messaggio campione 1

Questo e' il corpo.
From (non viene preceduta da sequenza di escape).
Ci sono tre righe.

********************
Cartelle Maildir

Le sottodirectory o cartelle di una casella di posta Maildir possono essere gestite direttamente con i metodi della classe Maildir. I chiamanti possono elencare, recuperare, creare e rimuovere sottocartelle per una data casella di posta.

# mailbox_maildir_folders.py

import mailbox
import os


def show_maildir(name):
    os.system('find {} -print'.format(name))


mbox = mailbox.Maildir('Esempio')
print('Prima:', mbox.list_folders())
show_maildir('Esempio')

print('\n{:#^30}\n'.format(''))

mbox.add_folder('sottocartella')
print('creata sottocartella:', mbox.list_folders())
show_maildir('Esempio')

subfolder = mbox.get_folder('sottocartella')
print('sottocartella contiene:', subfolder.list_folders())

print('\n{:#^30}\n'.format(''))

subfolder.add_folder('secondo_livello')
print('creato secondo_livello:', subfolder.list_folders())
show_maildir('Esempio')

print('\n{:#^30}\n'.format(''))

subfolder.remove_folder('secondo_livello')
print('secondo_livello rimosso:', subfolder.list_folders())
show_maildir('Esempio')

Il nome della directory per la cartella viene costruito prefissando il nome della cartella con un punto (. ).

$ python3 mailbox_maildir_folders.py

Prima: []
Esempio
Esempio/new
Esempio/tmp
Esempio/cur
Esempio/cur/1573461573.M72514P9228Q1.robby-System-Product-Name

##############################

creata sottocartella: ['sottocartella']
Esempio
Esempio/new
Esempio/.sottocartella
Esempio/.sottocartella/new
Esempio/.sottocartella/tmp
Esempio/.sottocartella/cur
Esempio/.sottocartella/maildirfolder
Esempio/tmp
Esempio/cur
Esempio/cur/1573461573.M72514P9228Q1.robby-System-Product-Name
sottocartella contiene: []

##############################

creato secondo_livello: ['secondo_livello']
Esempio
Esempio/new
Esempio/.sottocartella
Esempio/.sottocartella/new
Esempio/.sottocartella/tmp
Esempio/.sottocartella/cur
Esempio/.sottocartella/.secondo_livello
Esempio/.sottocartella/.secondo_livello/new
Esempio/.sottocartella/.secondo_livello/tmp
Esempio/.sottocartella/.secondo_livello/cur
Esempio/.sottocartella/.secondo_livello/maildirfolder
Esempio/.sottocartella/maildirfolder
Esempio/tmp
Esempio/cur
Esempio/cur/1573461573.M72514P9228Q1.robby-System-Product-Name

##############################

secondo_livello rimosso: []
Esempio
Esempio/new
Esempio/.sottocartella
Esempio/.sottocartella/new
Esempio/.sottocartella/tmp
Esempio/.sottocartella/cur
Esempio/.sottocartella/maildirfolder
Esempio/tmp
Esempio/cur
Esempio/cur/1573461573.M72514P9228Q1.robby-System-Product-Name

Segnalatori di Messaggio

I messaggi nelle caselle di posta hanno segnalatori per tenere traccia di vari aspetti, tipo il fatto che il messaggio sia stato letto oppure no, che un messaggio sia stato contrassegnato come importante o meno, o marcato per una successiva cancellazione. I segnalatori sono conservati come sequenza di specifici codici lettera e le classi Message hanno metodi per recuperarli e modificarne il valore. Questo esempio mostra i segnalatori per i messaggi nella casella di posta Esempio prima di aggiungere un segnalatore che indica che quel messaggio è considerato importante.

# mailbox_maildir_add_flag.py

import mailbox

print('Prima:')
mbox = mailbox.Maildir('Esempio')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_flags(),
                                 message['subject']))
        message.add_flag('F')
        # Si chiede a milabox di aggiornare il messaggio
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

print('\nDopo:')
mbox = mailbox.Maildir('Esempio')
for message in mbox:
    print('{:6} "{}"'.format(message.get_flags(),
                             message['subject']))

Nella modalità predefinita, i messaggi non hanno segnalatori. L'aggiunta di un segnalatore cambia il messaggio in memoria, ma non aggiorna la versione su disco. Per aggiornare il messaggio su disco si salvi l'oggetto messaggio nella casella di posta usando il suo identificatore.

$ python3 mailbox_maildir_add_flag.py

Prima:
       "Messaggio campione 1"

Dopo:
F      "Messaggio campione 1"

Aggiungere segnalatori con add_flag() preserva eventuali segnalatori già esistenti. Si utilizzi set_flags() per sovrascrivere qualsiasi segnalatore esistente, sostituendoli con i nuovi valori passati al metodo.

# mailbox_maildir_set_flags.py

import mailbox

print('Prima:')
mbox = mailbox.Maildir('Esempio')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_flags(),
                                 message['subject']))
        message.set_flags('S')
        # Si chiede a mailbox di aggiornare il messaggio
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

print('\nDopo:')
mbox = mailbox.Maildir('Esempio')
for message in mbox:
    print('{:6} "{}"'.format(message.get_flags(),
                             message['subject']))

Il segnalatore F aggiunto con l'esempio precedente viene perso quando set_flags() sostituisce i segnalatori con S in questo esempio.

$ python3 mailbox_maildir_set_flags.py
Prima:
F      "Messaggio campione 1"

Dopo:
S      "Messaggio campione 1"

Altri Formati

mailbox supporta qualche altro formato, ma nessuno di essi è conosciuto come mbox o Maildir. MH è un altro formato di casella di posta multi file, usato da alcuni gestori di mail. Babyl e MMDF sono formati a file singolo con separatori di messaggio diversi da quelli di mbox. I formati a singolo file supportano la stessa API di mbox, e MH include i metodi per la gestione delle cartelle visti nella classe Maildir.

Vedere anche:

mailbox
La documentazione della libreria standard per questo modulo.
Note di portabilità da Python 2 a 3 per mailbox
Mbox
Pagina di wikipedia per il formato Mbox (inglese)
Maildir
Pagina di wikipedia per il formato Maildir (inglese)
email
Il modulo email
imaplib
Il modulo imaplib può lavorare con messaggi email salvati su di un server IMAP