If you benefit from web2py hope you feel encouraged to pay it forward by contributing back to society in whatever form you choose!

Perhaps not too many users know this, since it was recently added to official docs, but it's actually possible to read, update and delete IMAP mailbox messages from our web2py apps with dal IMAPAdapter class. In this recipe, I'll guide you trough the very simple steps required to handle email messages in your app with this very poweful special adapter. Check the class help in gluon for more details. Also, the feature is documented in the web2py book in the email and sms chapter.


First of all, IMAPAdapter is not a standard DAL interface, because normal IMAP server implementations do not support common db tasks such as updating any table field or doing record inserts. For example, you cannot define your own mailbox tables, because they need to be read and set from the server account information automatically. However, the adapter provides a DAL interface to read mailboxes and perform other administration tasks like updating flags or deleting unwanted messages.



For a single mail account, this is the code recommended to start imap support at the app's model

# Replace user, password, server and port in the connection string
# Set port as 993 for ssl support
imapdb = DAL("imap://<user@account>:<password>@<server>:<port>", pool_size=1)
# You need this command to access the server mailboxes CRUD

Fetching mail and updating flags

To count today's unseen messages smaller than 6000 octets from the inbox mailbox do

q = imapdb.INBOX.seen == False
q &= imapdb.INBOX.created == datetime.date.today()
q &= imapdb.INBOX.size < 6000
unread = imapdb(q).count()


You can fetch (select) the previous query messages with

rows = imapdb(q).select()


Usual query operators are implemented, including belongs

messages = imapdb(imapdb.INBOX.uid.belongs(<uid sequence>)).select()


Note: It's strongly adviced that you keep the query results below a given data size threshold to avoid jamming the server with large select commands. As of now, the messages are retrieved entirely by the adapter before any filter by field can be applied.

It is possible to filter query select results with limitby and sequences of mailbox fields

# Replace the arguments with actual values
myset.select(<fields sequence>, limitby=(<int>, <int>))


Say you want to have an app action show a mailbox message. First we retrieve the message (If your IMAP service supports it, fetch messages by uid field to avoid using old sequence references).

mymessage = imapdb(imapdb.INBOX.uid == <uid>).select().first()


Finally, add something like the following to show the message content in a view

{{=P(T("Message from"), " ", mymessage.sender)}}
{{=P(T("Received on"), " ", mymessage.created)}}
{{for text in mymessage.content:}}


To mark (update) last query messages as seen

seen = imapdb(q).update(seen=True)


Here we delete messages in the imap database that have mails from mr. Gumby

deleted = 0
for tablename in imapdb.tables():
    deleted += imapdb(imapdb[tablename].sender.contains("gumby")).delete()


Related slices

Comments (1)

  • Login to post

  • 0
    spametki 7 years ago

    As of now, the messages are retrieved entirely by the adapter before any filter by field can be applied.

    This was changed in last trunk updates. Now selecting rows with a subset of columns (i.e: using a list of fields) can reduce the total payload retrieved. Queries not including fields like .attachments, .size, .email or .content will be much faster

Hosting graciously provided by:
Python Anywhere