Package: imap-tools
High level lib for work with email by IMAP:Basic message operations: fetch, uids, numbersParsed email message attributesQuery builder for search criteriaActions with emails: copy, delete, flag, move, appendActions with folders: list, set, get, create, exists, rename, subscribe, delete, statusIDLE commands: start, poll, stop, waitExceptions on failed IMAP operationsNo external dependencies, testedPython version3.5+LicenseApache-2.0PyPIhttps://pypi.python.org/pypi/imap_tools/RFCIMAP4.1,EMAIL,IMAP related RFCsContentsInstallationGuideBasicEmail attributesSearch criteriaActions with emailsActions with foldersIDLE workflowExceptionsRelease notesContributeReasonsThanksHelp the projectInstallation$ pip install imap-toolsGuideBasicInfo about lib are at:this page, docstrings, issues, pull requests, examples, source, stackoverflow.comfromimap_toolsimportMailBox,AND# Get date, subject and body len of all emails from INBOX folderwithMailBox('imap.mail.com').login('test@mail.com','pwd')asmailbox:formsginmailbox.fetch():print(msg.date,msg.subject,len(msg.textormsg.html))Description of this^ example.MailBox, MailBoxTls, MailBoxUnencrypted - for create mailbox client.TLS example.BaseMailBox.<auth> - login, login_utf8, xoauth2, logout - authentication functions, they support context manager.BaseMailBox.fetch - first searches email uids by criteria in current folder, then fetch and yieldsMailMessage, args:criteria= ‘ALL’, message search criteria,query buildercharset= ‘US-ASCII’, indicates charset of the strings that appear in the search criteria. See rfc2978limit= None, limit on the number of read emails, useful for actions with a large number of messages, like “move”mark_seen= True, mark emails as seen on fetchreverse= False, in order from the larger date to the smallerheaders_only= False, get only email headers (without text, html, attachments)bulk= False, False - fetch each message separately per N commands - low memory consumption, slow; True - fetch all messages per 1 command - high memory consumption, fastBaseMailBox.uids - search mailbox for matching message uids in current folder, returns [str | None], None when MailMessage.from_bytes used, args:criteria= ‘ALL’, message search criteria,query buildercharset= ‘US-ASCII’, indicates charset of the strings that appear in the search criteria. See rfc2978BaseMailBox.<action> -copy, move, delete, flag, appendBaseMailBox.folder -folder managerBaseMailBox.idle -idle managerBaseMailBox.numbers - search mailbox for matching message numbers in current folder, returns [str]BaseMailBox.client - imaplib.IMAP4/IMAP4_SSL client instance.Email attributesMailMessage and MailAttachment public attributes are cached by functools.lru_cacheformsginmailbox.fetch():# generator: imap_tools.MailMessagemsg.uid# str | None: '123'msg.subject# str: 'some subject 你 привет'msg.from_# str: 'Bartölke@ya.ru'msg.to# tuple: ('iam@goo.ru', 'friend@ya.ru', )msg.cc# tuple: ('cc@mail.ru', )msg.bcc# tuple: ('bcc@mail.ru', )msg.reply_to# tuple: ('reply_to@mail.ru', )msg.date# datetime.datetime: 1900-1-1 for unparsed, may be naive or with tzinfomsg.date_str# str: original date - 'Tue, 03 Jan 2017 22:26:59 +0500'msg.text# str: 'Hello 你 Привет'msg.html# str: '<b>Hello 你 Привет</b>'msg.flags# tuple: ('\\Seen', '\\Flagged', 'ENCRYPTED')msg.headers# dict: {'received': ('from 1.m.ru', 'from 2.m.ru'), 'anti-virus': ('Clean',)}msg.size_rfc822# int: 20664 bytes - size info from server (*useful with headers_only arg)msg.size# int: 20377 bytes - size of received messageforattinmsg.attachments:# list: imap_tools.MailAttachmentatt.filename# str: 'cat.jpg'att.payload# bytes: b'\xff\xd8\xff\xe0\'att.content_id# str: 'part45.06020801.00060008@mail.ru'att.content_type# str: 'image/jpeg'att.content_disposition# str: 'inline'att.part# email.message.Message: original objectatt.size# int: 17361 bytesmsg.obj# email.message.Message: original objectmsg.from_values# imap_tools.EmailAddress | Nonemsg.to_values# tuple: (imap_tools.EmailAddress,)msg.cc_values# tuple: (imap_tools.EmailAddress,)msg.bcc_values# tuple: (imap_tools.EmailAddress,)msg.reply_to_values# tuple: (imap_tools.EmailAddress,)# EmailAddress(name='Ya', email='im@ya.ru')  # "full" property = 'Ya <im@ya.ru>'Search criteriaYou can pass“criteria”argument to 3 methods of MailBox:fetch, uids, numbers. Criteria can be of three types:fromimap_toolsimportANDmailbox.fetch(AND(subject='weather'))# query, the str-like objectmailbox.fetch('TEXT "hello"')# strmailbox.fetch(b'TEXT "\xd1\x8f"')# bytesUse“charset”argument for encode criteria to the desired encoding. If criteria is bytes - encoding will be ignored.mailbox.uids(A(subject='жёлтый'),charset='utf8')Query builder implements all search logic described inrfc3501.
It uses this classes:ClassAliasDescriptionArgumentsANDACombine conditions by logical “AND”Search keys (see table below) | strOROCombine conditions by logical “OR”Search keys (see table below) | strNOTNInvert the result of a logical expressionAND/OR instances | strHeaderHHeader value for search by header keyname: str, value: strUidRangeUUID range value for search by uid keystart: str, end: strSeequery examples. A few examples:fromimap_toolsimportA,AND,OR,NOT# ANDA(text='hello',new=True)# '(TEXT "hello" NEW)'# OROR(text='hello',date=datetime.date(2000,3,15))# '(OR TEXT "hello" ON 15-Mar-2000)'# NOTNOT(text='hello',new=True)# 'NOT (TEXT "hello" NEW)'# complexA(OR(from_='from@ya.ru',text='"the text"'),NOT(OR(A(answered=False),A(new=True))),to='to@ya.ru')# python note: you can't do: A(text='two', NOT(subject='one'))A(NOT(subject='one'),text='two')# use kwargs after logic classes (args)Search key table. Key types marked with*can accepts a sequence of values like list, tuple, set or generator.KeyTypesResultsDescriptionansweredboolANSWERED/UNANSWEREDwith/without the Answered flagseenboolSEEN/UNSEENwith/without the Seen flagflaggedboolFLAGGED/UNFLAGGEDwith/without the Flagged flagdraftboolDRAFT/UNDRAFTwith/without the Draft flagdeletedboolDELETED/UNDELETEDwith/without the Deleted flagkeywordstr*KEYWORD KEYwith the specified keyword flagno_keywordstr*UNKEYWORD KEYwithout the specified keyword flagfrom_str*FROM“from@ya.ru”contain specified str in envelope struct’s FROM fieldtostr*TO“to@ya.ru”contain specified str in envelope struct’s TO fieldsubjectstr*SUBJECT “hello”contain specified str in envelope struct’s SUBJECT fieldbodystr*BODY “some_key”contain specified str in body of the messagetextstr*TEXT “some_key”contain specified str in header or body of the messagebccstr*BCC“bcc@ya.ru”contain specified str in envelope struct’s BCC fieldccstr*CC“cc@ya.ru”contain specified str in envelope struct’s CC fielddatedatetime.date*ON 15-Mar-2000internal date is within specified datedate_gtedatetime.date*SINCE 15-Mar-2000internal date is within or later than the specified datedate_ltdatetime.date*BEFORE 15-Mar-2000internal date is earlier than the specified datesent_datedatetime.date*SENTON 15-Mar-2000rfc2822 Date: header is within the specified datesent_date_gtedatetime.date*SENTSINCE 15-Mar-2000rfc2822 Date: header is within or later than the specified datesent_date_ltdatetime.date*SENTBEFORE 1-Mar-2000rfc2822 Date: header is earlier than the specified datesize_gtint >= 0LARGER 1024rfc2822 size larger than specified number of octetssize_ltint >= 0SMALLER 512rfc2822 size smaller than specified number of octetsnewTrueNEWhave the Recent flag set but not the Seen flagoldTrueOLDdo not have the Recent flag setrecentTrueRECENThave the Recent flag setallTrueALLall, criteria by defaultuiditer(str)/str/UUID 1,2,17corresponding to the specified unique identifier setheaderH(str, str)*HEADER “A-Spam” “5.8”have a header that contains the specified str in the textgmail_labelstr*X-GM-LABELS “label1”have this gmail labelServer side search notes:For string search keys a message matches if the string is a substring of the field. The matching is case-insensitive.When searching by dates - email’s time and timezone are disregarding.Actions with emailsFirst of all read about UIDat rfc3501.Action’s uid_list arg may takes:str, that is comma separated uidsSequence, that contains str uidsTo get uids, use the maibox methods: uids, fetch.For actions with a large number of messages imap command may be too large and will cause exception at server side,
use ‘limit’ argument for fetch in this case.withMailBox('imap.mail.com').login('test@mail.com','pwd',initial_folder='INBOX')asmailbox:# COPY messages with uid in 23,27 from current folder to folder1mailbox.copy('23,27','folder1')# MOVE all messages from current folder to INBOX/folder2mailbox.move(mailbox.uids(),'INBOX/folder2')# DELETE messages with 'cat' word in its html from current foldermailbox.delete([msg.uidformsginmailbox.fetch()if'cat'inmsg.html])# FLAG unseen messages in current folder as \Seen, \Flagged and TAG1flags=(imap_tools.MailMessageFlags.SEEN,imap_tools.MailMessageFlags.FLAGGED,'TAG1')mailbox.flag(mailbox.uids(AND(seen=False)),flags,True)# APPEND: add message to mailbox directly, to INBOX folder with \Seen flag and now datewithopen('/tmp/message.eml','rb')asf:msg=imap_tools.MailMessage.from_bytes(f.read())# *or use bytes instead MailMessagemailbox.append(msg,'INBOX',dt=None,flag_set=[imap_tools.MailMessageFlags.SEEN])Actions with foldersBaseMailBox.login/xoauth2 has initial_folder arg, that is “INBOX” by default, use None for not set folder on login.withMailBox('imap.mail.com').login('test@mail.com','pwd')asmailbox:# LIST: get all subfolders of the specified folder (root by default)forfinmailbox.folder.list('INBOX'):print(f)# FolderInfo(name='INBOX|cats', delim='|', flags=('\\Unmarked', '\\HasChildren'))# SET: select folder for workmailbox.folder.set('INBOX')# GET: get selected foldercurrent_folder=mailbox.folder.get()# CREATE: create new foldermailbox.folder.create('INBOX|folder1')# EXISTS: check is folder exists (shortcut for list)is_exists=mailbox.folder.exists('INBOX|folder1')# RENAME: set new name to foldermailbox.folder.rename('folder3','folder4')# SUBSCRIBE: subscribe/unsubscribe to foldermailbox.folder.subscribe('INBOX|папка два',True)# DELETE: delete foldermailbox.folder.delete('folder4')# STATUS: get folder status infostat=mailbox.folder.status('some_folder')print(stat)# {'MESSAGES': 41, 'RECENT': 0, 'UIDNEXT': 11996, 'UIDVALIDITY': 1, 'UNSEEN': 5}IDLE workflowIDLE logic are in mailbox.idle manager, its methods are in the table below:MethodDescriptionArgumentsstartSwitch on mailbox IDLE modepollPoll for IDLE responsestimeout: Optional[float]stopSwitch off mailbox IDLE modewaitSwitch on IDLE, poll responses, switch off IDLE on response, return responsestimeout: Optional[float]fromimap_toolsimportMailBox,A# waiting for updates 60 sec, print unseen immediately if any updatewithMailBox('imap.my.moon').login('acc','pwd','INBOX')asmailbox:responses=mailbox.idle.wait(timeout=60)ifresponses:formsginmailbox.fetch(A(seen=False)):print(msg.date,msg.subject)else:print('no updates in 60 sec')Read docstrings and seedetailed examples.ExceptionsMost lib server actions raises exception if result is marked as not success.Custom lib exceptions here:errors.py.Release notesHistory of important changes:release_notes.rstContributeIf you found a bug or have a question, then:Look for answer at: this page, issues, pull requests, examples, source, RFCs, stackoverflow.com, internet.And only then - create merge request or issue.ReasonsExcessive low level ofimapliblibrary.Other libraries contain various shortcomings or not convenient.Open source projects make world better.ThanksBig thanks to people who helped develop this library:shilkazx,somepad,0xThiebaut,TpyoKnig,parchd-1,dojasoncom,RandomStrangerOnTheInternet,jonnyarnold,Mitrich3000,audemed44,mkalioby,atlas0fd00m,unqx,daitangio,upils,Foosec,frispete,PH89,amarkham09,nixCodeX,backelj,ohayak,mwherman95926,andyfensham,mike-code,aknrdureegaesr,ktulinger,SamGenTLEManKaka,devkral,tnusraddinov,thepeshka,shofstet,the7erm,c0da,dev4max,ascheucher,Borutia,nathan30,daniel55411,rcarmo,bhernacki,ilep,ThKue,repodiac,tiuub,Yannik,pete312,edkedk99,UlisseMini,Nicarex,RanjithNair1980,NickC-NZ,mweinelt,lucbouge,JacquelinCharbonnel,stumpylog,dimitrisstr,abionics,link2xtHelp the projectFound a bug or figure out how to improve the library - open issue or merge request 🎯Do not know how to improve library - try to help other open projects that you use ✋Nowhere to put your money - spend it on your family, friends, loved ones, or people around you 💰Star the project ⭐