Welcome to MailBot’s documentation!¶
MailBot is a little python library that let’s you execute previously registered callbacks on reception of emails.
This allows you to do fancy things like doing API calls, running scripts, sending notifications, ...
Features¶
MailBot does its best to:
- be fully tested
- apply the pep8 recommendations
- be lightweight, concise and readable
MailBot connects to a mail server using the IMAP protocol, thanks to the excellent IMAPClient from Menno Smits.
Other resources¶
Fork it on: http://github.com/magopian/mailbot/
Documentation: http://mailbot.rtfd.org/
Installing¶
From PyPI:
pip install mailbot
From github:
pip install -e http://github.com/magopian/mailbot/
Usage¶
You first need to instantiate MailBot, giving it informations to connect to your IMAP server. MailBot uses IMAPClient, and as such takes the same parameters.
You also need to provide the username and password. Here’s an simple example:
from mailbot import MailBot, register
from mycallbacks import MyCallback
mailbot = MailBot('imap.myserver.com', 'username', 'password')
# register your callback
register(MyCallback)
# check the unprocessed messages and trigger the callback
mailbot.process_messages()
You may want to place the process_messages
in a loop, a celery task, or in
a cron job, tu regularly check new messages and process them.
Registering callbacks¶
callbacks.py
:
from mailbot import register, Callback
class MyCallback(Callback):
def trigger(self):
print("Mail received: {O}".format(self.subject))
register(MyCallback)
By default, callbacks will be executed on each and every mail received, unless you specify it differently, either using the ‘rules’ attribute on the callback class, or by registering with those rules:
Providing the rules as a parameter¶
Here’s a callback that will only be triggered if the subject matches the
pattern ‘Hello ‘ followed by a word, anywhere in the subject (it uses
re.findall
):
from mailbot import register, Callback
class MyCallback(Callback):
rules = {'subject': [r'Hello (\w)']}
def trigger(self):
print("Mail received for {0}".format(self.matches['subject'][0]))
register(MyCallback)
This callback will be triggered on a mail received with the subject “Hello Bryan”, but won’t if the subject is “Bye Bryan”.
Providing the rules when registering¶
The similar functionality can be achieved using a set of rules when registering:
from mailbot import register, Callback
class MyCallback(Callback):
def trigger(self):
print("Mail received for %s!" self.matches['subject'][0])
register(MyCallback, rules={'subject': [r'Hello (\w)']})
How does it work?¶
When an email is received on the mail server the MailBot is connected to (using the IMAP protocol), it’ll check all the registered callbacks and their rules.
If each provided rule (either as a class parameter or using the register) matches the mail’s subject, from, to, cc and body, the callback will be triggered.
Mails are flagged according to their state, in the process_messages
method:
- unread (unseen): mail to be processed by MailBot
- read (seen):
- starred (flagged): MailBot is checking callbacks, and triggering them if needed, the mail is being processed
- not starred (unflagged): MailBot is done with this mail, and won’t process it anymore
Specifying a timeout¶
To avoid a mail from staying in the “processing” state for too long (for
example because a previous process_message
started processing it, but then
failed), you may specify a timeout
parameter (in seconds) when
instantiating MailBot:
from mailbot import MailBot
mailbot = MailBot('imap.myserver.com', 'username', 'password', timeout=180)
This doesn’t mean that the mail will be reset after 3 minutes, but that when
process_messages
is called, it’ll first reset mails that are in the
processing state and older than 3 minutes.
Specifying rules¶
Rules are regular expressions that will be tested against the various email data:
subject
: tested against the subjectfrom
: tested against the mail senderto
: tested against each of the recipients in the “to” fieldcc
: tested against each of the recipients in the “cc” fieldbody
: tested against the (text/plain) body of the mail
If no rule are provided, for example for the “from” field, then no rule will be applied, and emails from any sender will potentially trigger the callback.
For each piece of data (subject, from, to, cc, body), the callback class,
once instantiated with the mail, and the check_rules
method called, will
have the attribute self.matches[item]
set with all the captures from the
given patterns, if any, or the full match.
Here are example subjects for the subject rules:
[r'Hello (\w+), (.*)'
, r'[Hh]i (\w+)
]
For each of the following examples, self.matches['subject']
will be a list
of all the captures for all the regular expressions.
If a regular expression doesn’t match, then it’ll return an empty list.
- ‘Hello Bryan, how are you?’: [(‘Bryan’, ‘how are you?’)]
- ‘Hi Bryan, how are you?’: [‘Bryan’]
- ‘aloha, hi Bryan!’: [‘Bryan’]
- ‘aloha Bryan’: rules not respected, callback not triggered, []
Here are example subjects for the subject rules (no captures):
[r'Hello \w+'
, r'[Hh]i \w+
]
- ‘Hello Bryan, how are you?’: [‘Hello Bryan’]
- ‘Hi Bryan, how are you?’: [‘Hi Bryan’]
- ‘aloha, hi Bryan!’: [‘hi Bryan’]
- ‘aloha Bryan’: rules not respected, callback not triggered, []
Rules checking¶
A callback will be triggered if the following applies:
- for each item/rule, any of the provided regular expressions matches
- all the rules (for all the provided items) are respected
Notice the “any” and the “all” there:
- for each rule, there may be several regular expressions. If any of those match, then the rule is respected.
- if one rule doesn’t match, the callback won’t be triggered. Non existent rules don’t count, so you could have a single rule on the subject, and none on the other items (from, to, cc, body).
As an example, let’s take an email with the subject “Hello Bryan”, from “John@doe.com”:
from mailbot import register, Callback
class MyCallback(Callback):
rules = {'subject': [r'Hello (\w)', 'Hi!'], 'from': ['@doe.com']}
def trigger(self):
print("Mail received for {0}".format(self.matches['subject'][0]))
register(MyCallback)
All the rules are respected, and the callback will be triggered
- subject: even though ‘Hi!’ isn’t found anywhere in the subject, the other regular expression matches
- from: the regular expression matches
- to, cc, body: no rules provided, so they aren’t taken into account
The last bullet point also means that if register a callback with no rules at all, it’ll be triggered on each and every email, making it a “catchall callback”.