mailbox — Manipular archivos de correo electrónico

Propósito:Trabaja con mensajes de correo electrónico en varios formatos de archivo locales.

El módulo mailbox define una interfaz de aplicación común para acceder a mensajes de correo electrónico almacenados en formatos de disco local, que incluyen:

  • Maildir
  • mbox
  • MH
  • Babyl
  • MMDF

Hay clases base para Mailbox y Message, y cada formato de buzón incluye un par correspondiente de subclases para implementar los detalles para ese formato.

mbox

El formato mbox es el más sencillo de mostrar en la documentación, ya que es texto plano. Cada buzón se almacena como un solo archivo, con todos los mensajes concatenados juntos. Cada vez que se encuentra una línea comienza con "From" («From» seguido de un solo espacio) se trata como el comienzo de un nuevo mensaje. En cualquier momento los caracteres aparecen al comienzo de una línea en el cuerpo del mensaje, ellos se escapan prefijando la línea con ">".

Crear un buzón de mbox

Crea una instancia de la clase mbox pasando el nombre del archivo al constructor. Si el archivo no existe, se crea cuando add() se usa para agregar mensajes.

mailbox_mbox_create.py
import mailbox
import email.utils

from_addr = email.utils.formataddr(('Author',
                                    'author@example.com'))
to_addr = email.utils.formataddr(('Recipient',
                                  'recipient@example.com'))

payload = '''This is the body.
From (will not be escaped).
There are 3 lines.
'''

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

    msg = mailbox.mboxMessage()
    msg.set_unixfrom('author')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Sample message 2'
    msg.set_payload('This is the second body.\n')
    mbox.add(msg)
    mbox.flush()
finally:
    mbox.unlock()

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

El resultado de este script es un nuevo archivo de buzón con dos mensajes de correo electrónico.

$ python3 mailbox_mbox_create.py

From MAILER-DAEMON Sun Mar 18 20:20:59 2018
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 1

This is the body.
>From (will not be escaped).
There are 3 lines.

From MAILER-DAEMON Sun Mar 18 20:20:59 2018
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 2

This is the second body.

Leer un buzón de mbox

Para leer un buzón existente, ábrelo y trata el objeto mbox como un diccionario. Las llaves son valores arbitrarios definidos por la instancia del buzón y no son necesariamente significativos aparte de ser identificadores internos para objetos mensaje.

mailbox_mbox_read.py
import mailbox

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

El buzón abierto admite el protocolo iterador, pero a diferencia de un objetos de diccionario verdadero, el iterador predeterminado para un buzón funciona en los valores en lugar de las llaves.

$ python3 mailbox_mbox_read.py

Sample message 1
Sample message 2

Eliminar mensajes de un buzón de mbox

Para eliminar un mensaje existente de un archivo mbox, usa su llave con remove() o usa del.

mailbox_mbox_remove.py
import mailbox

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

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

Los métodos lock() y unlock() se utilizan para evitar problemas de el acceso simultáneo al archivo, y flush() fuerza que los cambios se escribirán en el disco.

$ python3 mailbox_mbox_remove.py

Removing: 1
From MAILER-DAEMON Sun Mar 18 20:20:59 2018
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 1

This is the body.
>From (will not be escaped).
There are 3 lines.

Maildir

El formato Maildir fue creado para eliminar el problema de modificación concurrente en un archivo mbox. En lugar de usar un solo archivo, el buzón está organizado como un directorio donde cada mensaje está contenido su propio archivo. Esto también permite que los buzones se aniden, por lo que la interfaz de programación para un buzón Maildir se amplía con métodos para trabajar con subcarpetas

Crear un buzón de correo Maildir

La única diferencia real entre crear un Maildir y mbox es que el argumento para el constructor es un nombre de directorio en lugar de un nombre de archivo. Como antes, si el buzón no existe, se crea cuando se agregan mensajes.

mailbox_maildir_create.py
import mailbox
import email.utils
import os

from_addr = email.utils.formataddr(('Author',
                                    'author@example.com'))
to_addr = email.utils.formataddr(('Recipient',
                                  'recipient@example.com'))

payload = '''This is the body.
From (will not be escaped).
There are 3 lines.
'''

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

    msg = mailbox.mboxMessage()
    msg.set_unixfrom('author Sat Feb  7 01:05:34 2009')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Sample message 2'
    msg.set_payload('This is the second body.\n')
    mbox.add(msg)
    mbox.flush()
finally:
    mbox.unlock()

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

Cuando los mensajes se agregan al buzón, van al subdirectorio new.

Advertencia

Aunque es seguro escribir en el mismo maildir desde múltiples procesos, add() no es seguro para subprocesos. Usa un semáforo o otro dispositivo de bloqueo para evitar modificaciones simultáneas en el buzón de múltiples subprocesos del mismo proceso.

$ python3 mailbox_maildir_create.py

Example
  Directories: ['new', 'cur', 'tmp']
Example/new
  Directories: []

*** Example/new/1521404460.M306174P41689Q2.hubert.local
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 2

This is the second body.

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

*** Example/new/1521404460.M303200P41689Q1.hubert.local
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 1

This is the body.
From (will not be escaped).
There are 3 lines.

********************
Example/cur
  Directories: []
Example/tmp
  Directories: []

Después de leerlos, un cliente podría moverlos al subdirectorio cur utilizando el método set_subdir() del MaildirMessage.

mailbox_maildir_set_subdir.py
import mailbox
import os

print('Before:')
mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_subdir(),
                                 message['subject']))
        message.set_subdir('cur')
        # Tell the mailbox to update the message.
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

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

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

Aunque un maildir incluye un directorio «tmp», los únicos argumentos válidos para set_subdir() son «cur» y «new».

$ python3 mailbox_maildir_set_subdir.py

Before:
new    "Sample message 2"
new    "Sample message 1"

After:
cur    "Sample message 2"
cur    "Sample message 1"

Example
  Directories: ['new', 'cur', 'tmp']
Example/new
  Directories: []
Example/cur
  Directories: []
Example/cur/1521404460.M306174P41689Q2.hubert.local
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
  Directories: []

Leer un buzón de correo de Maildir

Leer desde un buzón existente de Maildir funciona igual que un buzón mbox.

mailbox_maildir_read.py
import mailbox

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

No se garantiza que los mensajes se lean en ningún orden en particular.

$ python3 mailbox_maildir_read.py

Sample message 2
Sample message 1

Eliminar mensajes de un buzón de correo de Maildir

Para eliminar un mensaje existente de un buzón de correo de Maildir, pasa su llave a remove() o usa del.

mailbox_maildir_remove.py
import mailbox
import os

mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    to_remove = []
    for key, msg in mbox.iteritems():
        if '2' in msg['subject']:
            print('Removing:', 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('Example'):
    print(dirname)
    print('  Directories:', subdirs)
    for name in files:
        fullname = os.path.join(dirname, name)
        print('\n***', fullname)
        print(open(fullname).read())
        print('*' * 20)

No hay forma de calcular la llave de un mensaje, por lo tanto, usa items() o iteritems() para recuperar la llave y el objeto de mensaje del buzón al mismo tiempo.

$ python3 mailbox_maildir_remove.py

Removing: 1521404460.M306174P41689Q2.hubert.local
Example
  Directories: ['new', 'cur', 'tmp']
Example/new
  Directories: []
Example/cur
  Directories: []

*** Example/cur/1521404460.M303200P41689Q1.hubert.local
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 1

This is the body.
From (will not be escaped).
There are 3 lines.

********************
Example/tmp
  Directories: []

Carpetas Maildir

Se pueden administrar subdirectorios o carpetas de un buzón de correo de Maildir directamente a través de los métodos de la clase Maildir. Personas que llaman puede enumerar, recuperar, crear y eliminar subcarpetas para un determinado buzón.

mailbox_maildir_folders.py
import mailbox
import os


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


mbox = mailbox.Maildir('Example')
print('Before:', mbox.list_folders())
show_maildir('Example')

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

mbox.add_folder('subfolder')
print('subfolder created:', mbox.list_folders())
show_maildir('Example')

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

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

subfolder.add_folder('second_level')
print('second_level created:', subfolder.list_folders())
show_maildir('Example')

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

subfolder.remove_folder('second_level')
print('second_level removed:', subfolder.list_folders())
show_maildir('Example')

El nombre del directorio para la carpeta se construye con un punto (.) como prefijo del nombre de la carpeta.

$ python3 mailbox_maildir_folders.py

Example
Example/new
Example/cur
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
Example
Example/.subfolder
Example/.subfolder/maildirfolder
Example/.subfolder/new
Example/.subfolder/cur
Example/.subfolder/tmp
Example/new
Example/cur
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
Example
Example/.subfolder
Example/.subfolder/.second_level
Example/.subfolder/.second_level/maildirfolder
Example/.subfolder/.second_level/new
Example/.subfolder/.second_level/cur
Example/.subfolder/.second_level/tmp
Example/.subfolder/maildirfolder
Example/.subfolder/new
Example/.subfolder/cur
Example/.subfolder/tmp
Example/new
Example/cur
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
Example
Example/.subfolder
Example/.subfolder/maildirfolder
Example/.subfolder/new
Example/.subfolder/cur
Example/.subfolder/tmp
Example/new
Example/cur
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
Before: []

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

subfolder created: ['subfolder']
subfolder contents: []

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

second_level created: ['second_level']

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

second_level removed: []

Banderas de mensajes

Los mensajes en los buzones tienen banderas para rastrear aspectos tales como si o no, el mensaje ha sido leído, marcado como importante por el lector, o marcado para su eliminación más tarde. Las banderas se almacenan como una secuencia de códigos de letras específicos del formato y las clases Message tienen métodos para recuperar y cambiar los valores de las banderas. Este ejemplo muestra las banderas en los mensajes en el buzón Example antes de agregar la bandera para indicar que el mensaje se considera importante.

mailbox_maildir_add_flag.py
import mailbox

print('Before:')
mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_flags(),
                                 message['subject']))
        message.add_flag('F')
        # Tell the mailbox to update the message.
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

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

Por defecto los mensajes no tienen banderas. Agregar una bandera cambia el mensaje en la memoria, pero no actualiza el mensaje en el disco. Para actualizar el mensaje en disco almacena el objeto de mensaje en el maildir utilizando su identificador existente.

$ python3 mailbox_maildir_add_flag.py

Before:
       "Sample message 1"

After:
F      "Sample message 1"

Agregar banderas con add_flag() conserva cualquier bandera existente. Utilizar set_flags() escribe sobre cualquier conjunto existente de banderas, reemplazándolo con los nuevos valores pasados al método.

mailbox_maildir_set_flags.py
import mailbox

print('Before:')
mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_flags(),
                                 message['subject']))
        message.set_flags('S')
        # Tell the mailbox to update the message.
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

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

La bandera F agregada por el ejemplo anterior se pierde cuando set_flags() reemplaza las banderas con S en este ejemplo.

$ python3 mailbox_maildir_set_flags.py

Before:
F      "Sample message 1"

After:
S      "Sample message 1"

Otros formatos

mailbox admite algunos otros formatos, pero ninguno es tan popular como mbox o Maildir. MH es otro formato de buzón de archivos múltiples utilizado por algunos manejadores de correo. Babyl y MMDF son formatos de un solo archivo con diferentes separadores de mensajes que mbox. Los formatos de archivo único admiten la misma interfaz de programación que mbox, y MH incluye métodos relacionados a carpetas encontrados en la clase Maildir.

Ver también