Skip to content

Repository files navigation

mRpostman

CRAN_Status_Badge Downloads from the RStudio CRAN mirror Downloads from the RStudio CRAN mirror CRAN/METACRAN R-CMD-check

An IMAP Client for R

Overview

mRpostman is a session-based IMAP client that implements the full command sets of the IMAP4rev2 (RFC 9051) and IMAP4rev1 (RFC 3501) protocols, along with the optional extensions registered with the Internet Assigned Numbers Authority (IANA), allowing you to perform virtually all e-mail operations from within R. The aim of this package is to pave the way for email data analysis in R. To do so, mRpostman makes extensive use of the {curl} package and the libcurl C library. Since version 3.0.0, searches are written as plain R expressions through query(), and every method returns a value ready for the analysis stage.

mRpostman’s official website: https://allanvc.github.io/mRpostman/

Cite mRpostman: A. V. C. Quadros, “mRpostman: An IMAP Client for R”, Journal of Open Research Software, vol. 12, no. 1, p. 4, 2024, doi: 10.5334/jors.480. http. Refer to citation("mRpostman").

Important changes in 3.0.0

Version 3.0.0 is the product of a deep refactoring of the package, and a few changes are not backward compatible:

  • use_uid now defaults to TRUE: every method operates on message UIDs instead of sequence numbers by default. UIDs are stable identifiers (sequence numbers are renumbered whenever messages are expunged), so ids remain valid across sessions — but scripts that relied on sequence numbers under the old default must now pass use_uid = FALSE (once, at configure_imap(), if preferred).
  • R >= 4.1.0 is now required.
  • The fourteen search_*() methods and the AND()/OR() helpers are soft-deprecated: searches are now written as plain R expressions through query(), or with the criteria constructors combined by the native &, |, and ! operators.
  • The old attachment entry points (fetch_attachments(), fetch_attachments_list(), get_attachments(), list_attachments()) are also soft-deprecated, replaced by attachments(), attachments_manifest(), and extract_attachments().
  • The name, to_folder, and msg_uid arguments were unified as folder and msg_id throughout the API; the old names still work but warn once per session.
  • use_uid, mute, and retries can now be set once, in configure_imap(), as connection-level defaults.

Nothing was removed: the deprecated spellings keep working and emit lifecycle warnings pointing to their replacements. See NEWS.md for the complete list of changes.

Providers and their IMAP urls

Provider IMAP Server
Gmail imap.gmail.com
Office 365 outlook.office365.com*
Outlook.com (Hotmail and Live.com) imap-mail.outlook.com
Yahoo Mail imap.mail.yahoo.com
iCloud Mail imap.mail.me.com
AOL Mail imap.aol.com
Zoho Mail imap.zoho.com
Yandex Mail imap.yandex.com
GMX Mail imap.gmx.com
Mail.com imap.mail.com
FastMail imap.fastmail.com

* For Office 365 accounts, the username should be set as user@yourcompany.com or user@youruniversity.edu for example.

Introduction

From version 0.9.0.0 onward, mRpostman is implemented under the OO paradigm, based on an R6 class called ImapCon. Its derived methods, and a few independent functions enable the R user to perform a myriad of IMAP commands.

The main groups, in the shape the package has since 3.0.0:

  • connection: configure_imap() (including the connection-level defaults use_uid, mute, and retries), disconnect(), the reset_*() setters;
  • search: query(), an ordinary R expression such as con$query((subject == "budget" | "budget 3") & flag != "SEEN"); search() with the criteria constructors (string(), before(), flag(), verbatim(), …) combined by the native &, |, and !; the older search_*() methods and AND()/OR() keep working as deprecated spellings;
  • fetch and decoding: fetch_body(), fetch_header(), fetch_text(), fetch_metadata(), fetch_envelope(), fetch_bodystructure(), and the decoders clean_msg_text() and decode_mime_header();
  • attachments: attachments_manifest() (list without downloading), attachments() (download, guided by the BODYSTRUCTURE), and extract_attachments() for already-fetched messages;
  • mailbox management: folder listing/creation/renaming/status, flags, copy/move/delete, append_msg()/append_msgs(), expunge;
  • server-side computation and events: sort(), thread(), the esearch_*() aggregates, idle() and notify() on a dedicated raw-socket route, plus every capability extension registered with IANA (quota, ACL, metadata, CONDSTORE/QRESYNC, and the rest).

Supported IMAP commands and capabilities

The IMAP protocol has a mandatory core — the IMAP4rev1 commands defined in RFC 3501, revised and consolidated by IMAP4rev2 in RFC 9051, which every compliant server must implement — plus a set of optional extensions, each advertised by the server in its CAPABILITY response. mRpostman covers both. For the extension-based methods, mRpostman checks the server’s advertised capabilities and, if the required one is missing, raises an informative error instead of letting the server reply with a cryptic BAD Unknown command. You can inspect what your server supports with list_server_capabilities().

Core commands (RFC 3501 — always available)

IMAP command mRpostman method(s)
CAPABILITY list_server_capabilities()
NOOP noop()
CHECK check()
LOGIN / AUTHENTICATE configure_imap()
LOGOUT disconnect()
SELECT / EXAMINE select_folder() / examine_folder()
CREATE / DELETE / RENAME create_folder() / delete_folder() / rename_folder()
SUBSCRIBE / UNSUBSCRIBE subscribe_folder() / unsubscribe_folder()
LIST / LSUB list_mail_folders() / list_subscribed_folders()
STATUS status()
APPEND append_msg()
SEARCH search(), search_before(), search_since(), search_string(), … (all search_*)
FETCH fetch_body(), fetch_header(), fetch_text(), fetch_metadata(), fetch_attachments()
STORE add_flags(), remove_flags(), replace_flags()
COPY copy_msg()
CLOSE close_folder()
EXPUNGE expunge(), delete_msg()

Optional extensions (server-dependent — capability-checked)

IMAP command mRpostman method(s) Capability RFC
SORT sort() SORT 5256
THREAD thread() THREAD=REFERENCES / THREAD=ORDEREDSUBJECT / THREAD=REFS 5256
GETQUOTA / GETQUOTAROOT get_quota() / get_quota_root() QUOTA 9208
NAMESPACE namespace() NAMESPACE 2342
ID id() ID 2971
UNSELECT unselect_folder() UNSELECT 3691
LIST (special-use) list_special_use_folders() SPECIAL-USE 6154
MOVE move_msg() MOVE 6851
SEARCH RETURN (ESEARCH) search(esearch = TRUE), esearch_count(), esearch_min_id(), esearch_max_id() ESEARCH 4731
UID EXPUNGE, APPENDUID / COPYUID expunge(msg_uid = ...), append_msg() (returns the UID), copy_msg() / move_msg() ("copyuid" attribute) UIDPLUS 4315
LIST ... RETURN (STATUS ...) list_folders_status() LIST-STATUS 5819
SETQUOTA set_quota() QUOTA 9208
GETACL / SETACL / DELETEACL / LISTRIGHTS / MYRIGHTS get_acl() / set_acl() / delete_acl() / list_rights() / my_rights() ACL 4314
ENABLE enable() ENABLE 5161
SEARCH RETURN (SAVE) search(save = TRUE), then msg_id = "$" in fetch/flag/copy/move/delete methods SEARCHRES 5182
SORT RETURN (...) sort(return = ...) ESORT 5267
LIST ... RETURN (CHILDREN SUBSCRIBED SPECIAL-USE) list_mail_folders(detailed = TRUE) LIST-EXTENDED 5258
STATUS (SIZE) status(items = "SIZE"), list_folders_status(items = "SIZE") STATUS=SIZE 8438
FETCH (PREVIEW) fetch_preview(), fetch_metadata(attribute = "PREVIEW") PREVIEW 8970
FETCH (SAVEDATE), SEARCH SAVEDBEFORE/SAVEDON/SAVEDSINCE fetch_metadata(attribute = "SAVEDATE"), saved_before() / saved_on() / saved_since() SAVEDATE 8514
SELECT (CONDSTORE), STATUS (HIGHESTMODSEQ), FETCH (MODSEQ), FETCH ... (CHANGEDSINCE), STORE ... (UNCHANGEDSINCE), SEARCH MODSEQ select_folder(condstore = TRUE), status(items = "HIGHESTMODSEQ"), fetch_metadata(attribute = "MODSEQ", changed_since = ), add_flags()/replace_flags()/remove_flags() (unchanged_since = ), modseq() CONDSTORE 7162
SELECT (QRESYNC ...), UID FETCH ... (CHANGEDSINCE VANISHED) resync_folder(), fetch_changes() QRESYNC 7162
GETMETADATA / SETMETADATA get_metadata() / set_metadata() METADATA 5464
IDLE idle() (on a dedicated second connection) IDLE 2177
APPEND (multiple literals) append_msgs() MULTIAPPEND 3502
NOTIFY SET / NOTIFY NONE notify() NOTIFY 5465
FETCH (BINARY.PEEK[...]) fetch_binary() BINARY 3516
APPEND ... CATENATE append_catenate(), imap_url() CATENATE 4469
COMPRESS DEFLATE compress = TRUE in the raw-socket methods COMPRESS=DEFLATE 4978
LIST ... RETURN (MYRIGHTS) list_mail_folders(detailed = TRUE) (my_rights column) LIST-MYRIGHTS 8440
CREATE ... (USE (...)) create_folder(special_use = ...) CREATE-SPECIAL-USE 6154
AUTHENTICATE OAUTHBEARER configure_imap(oauth_mechanism = "OAUTHBEARER") AUTH=OAUTHBEARER 7628
SORT (display keys) sort(by = "DISPLAYFROM"/"DISPLAYTO") SORT=DISPLAY 5957
SEARCH RETURN (PARTIAL m:n) esearch_partial() PARTIAL / CONTEXT=SEARCH 9394 / 5267
SORT RETURN (PARTIAL m:n) esort_partial() * CONTEXT=SORT 5267
REPLACE replace_msg() * REPLACE 8508
FETCH (EMAILID THREADID), STATUS (MAILBOXID) fetch_objectid() *, status(items = "MAILBOXID") * OBJECTID 8474
UIDBATCHES uid_batches() * UIDBATCHES 10022
ESEARCH IN (...) esearch_multi() * MULTISEARCH 7377
UNAUTHENTICATE unauthenticate() * UNAUTHENTICATE 8437
LANGUAGE / COMPARATOR language() * / comparator() * LANGUAGE / I18NLEVEL=2 5255
GENURLAUTH / URLFETCH genurlauth() * / urlfetch() * URLAUTH 4467
CONVERT fetch_convert() * CONVERT 5259
FETCH ANNOTATION / STORE ANNOTATION fetch_annotation() * / store_annotation() * ANNOTATE-EXPERIMENT-1 5257
SEARCH ... FUZZY fuzzy() criterion modifier * SEARCH=FUZZY 6203
SEARCH ... FILTER filter_stored() criterion * FILTERS 5466
APPEND size guard, STATUS (APPENDLIMIT) automatic in append_msg()/append_msgs(); status(items = "APPENDLIMIT") APPENDLIMIT 7889
non-synchronizing literals automatic on the raw-socket methods LITERAL+ / LITERAL- 7888

Every capability registered with IANA is covered. The methods marked with an asterisk (*) are experimental: they follow the RFC grammars, but no widely deployed server advertises those capabilities (they are rare, brand new, or were never adopted), so they could not be exercised against a live server. Availability of the others varies by provider: Gmail, for instance, supports every non-experimental extension above except SORT and THREAD, which it has never implemented; to exercise sort() and thread() you need a server that advertises them (e.g. Dovecot-based hosts, Yandex, or Outlook/Office 365). Announcement-only capabilities (AUTH=, LOGINDISABLED, RIGHTS=, QUOTA=, APPENDLIMIT=n, MESSAGELIMIT=/SAVELIMIT=, IMAPSIEVE=, JMAPACCESS, INPROGRESS, CHILDREN, I18NLEVEL=1, referrals) require no dedicated command and are honored where they matter (folder listings, appends, error reporting). UIDONLY (RFC 9586) responses (UIDFETCH) are understood after enable("UIDONLY"). The raw-socket methods (idle(), notify(), append_msgs(), append_catenate(), fetch_binary(), replace_msg(), esearch_multi(), urlfetch(), fetch_convert()) need an imaps:// URL for TLS, since STARTTLS is not available on that connection.

Installation

# CRAN version
install.packages("mRpostman")

# Dev version
if (!require('remotes')) install.packages('remotes')
remotes::install_github("allanvc/mRpostman")

mRpostman is built on the libcurl C library and requires libcurl >= 7.58.0 (>= 7.65.0 for OAuth2.0 authentication) — any recent Linux distribution satisfies this through libcurl4-openssl-dev (deb) or libcurl-devel (rpm); the macOS and Windows R builds already ship a suitable version.

Trying it without a mail account

The package ships a disposable local IMAP server (Dovecot, in a Docker container) plus a deterministic synthetic corpus generator, so every feature can be exercised offline and reproducibly — no credentials, OAuth2 setup, or provider rate limits involved:

# after starting the container (see the "sandbox" vignette):
con <- configure_imap(url = "imap://localhost:1430", username = "testuser",
                      password = "sandbox", use_ssl = FALSE)
populate_sandbox(con, n = 200) # uploads the corpus with the package's own APPEND

Real data works too: ingest_maildir() uploads any local maildir-style directory to the server via APPEND, and enron_sandbox() builds on it to download (once, with consent, cached) the public Enron corpus and ingest a subset selected by custodian, folder, and date — turning the sandbox into a full e-mail data-analysis laboratory.

See the “A reproducible IMAP sandbox with Docker” vignette for the guided tour. The sandbox’s Dovecot server also advertises SORT and THREAD, making it a convenient place to try the extensions your provider may lack.

Basic Usage

1) Connect

library(mRpostman)

con <- configure_imap(url = "imaps://outlook.office365.com",
                      username = "your_user@company.com",
                      password = rstudioapi::askForPassword())
# works with any provider from the table above (Gmail, Yahoo, Yandex, ...) and
# with self-hosted servers; use_uid, mute, and retries can also be set here,
# once, as connection-level defaults

con$list_server_capabilities()

Most providers no longer accept plain account passwords from third-party apps: you will typically authenticate with OAuth2.0 (the xoauth2_bearer argument) or with an app-specific password generated in your account’s security settings. The “Using IMAP OAuth2.0 authentication in mRpostman” vignette covers the OAuth2.0 setup for Gmail and other providers.

2) Search with plain R expressions

con$select_folder("INBOX")

# subject contains "budget" or "budget 3", and the message is unread
ids <- con$query((subject == "budget" | "budget 3") & flag != "SEEN")

# sent in Q4 2021 and larger than 5 MB
ids <- con$query(sent >= "2021-10-01" & sent < "2022-01-01" & size > 5e6)

Fields cover subject, from, to, body, text, flag, size, age, the date families, modseq, and header("Name"); see ?query for the full table. Raw protocol fragments enter through verbatim(), as in con$query(verbatim('X-GM-RAW "has:attachment"') & flag != "SEEN") on Gmail. The pre-3.0.0 search_*() methods keep working as deprecated spellings.

3) Fetch the matches as analysis-ready text

texts <- con$query(subject == "invoice" & age < 30 * 86400) %>%
  con$fetch_text() %>%
  clean_msg_text() # transfer encoding and charset decoded, plain character vector

4) Attachments

ids <- con$query(size > 1e6)

con$attachments_manifest(ids)          # list without downloading
con$attachments(ids, dest = "~/att")   # download, one folder per message

extract_attachments() does the same extraction offline, from messages already fetched with fetch_body().

More

Server-side sort() and thread(), the esearch_*() aggregates, idle()/notify() push notifications, quota, ACL, and the other IANA extensions are all methods on the same object; the basics vignette walks the full surface, and the sandbox vignette lets you try everything against a local disposable server, no account needed.

Future Improvements

  • a companion package with an interactive front-end for query building;
  • eliminate the {stringr} dependency in REGEX;
  • implement a progress bar in fetch operations;

Known bugs

  • search results truncation: This is a libcurl’s known bug which causes the search results to be truncated when there is a large number of message ids returned. To circumvent this problem, you can set a higher buffersize value, increasing the buffer capacity, and verbose = TRUE for monitoring the server response for truncated results when executing a search. When possible, mRpostman tries to issue a warning for possible truncated values.

  • verbose = TRUE malfunction on Windows: This seems to be related to the {curl} R package. When using the verbose = TRUE on Windows, the flow of information between the IMAP server and the R session presents an intermittent behavior, which causes it to not be shown on the console, or with a considerable delay.

  • shared mailbox access not working: This seems to be another libcurl’s bug, although more tests need to be done to confirm it. It does not allow the user to connect to a shared mailbox. To circumvent this, if the shared mailbox has a password associated with it, you can try a direct regular connection.

  • xoauth2_bearer SASL error: This is related to old libcurl’s versions which causes the access token to not be properly passed to the server. This bug was fixed in libcurl 7.65.0. The problem is that many Linux distributions, such as Ubuntu 18.04, still provide libcurl 7.58.0 in their official distribution (libcurl4-openssl-dev). If you use a newer Linux distro such as Ubuntu 20.04, you should be fine as the distributed libcurl’s version will be above 7.65.0. Another alternative is to use plain authentication instead of OAuth2.0.

License

This package is licensed under the terms of the GPL-3 License.

References

Crispin, M. (2003), INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1, RFC 3501, March 2003, http.

Heinlein, P. and Hartleben, P. (2008). The Book of IMAP: Building a Mail Server with Courier and Cyrus. No Starch Press. ISBN 978-1-59327-177-0.

Ooms, J. (2020), curl: A Modern and Flexible Web Client for R. R package version 4.3, http.

Quadros, A. V. C. mRpostman: An IMAP Client for R, Journal of Open Research Software, vol. 12, no. 1, p. 4, 2024, doi: 10.5334/jors.480. http.

Stenberg, D. Libcurl - The Multiprotocol File Transfer Library, http.

About

An IMAP Client for R

Resources

Stars

32 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages