imap-simple
Version:
Wrapper over node-imap, providing a simpler api for common use cases
435 lines (353 loc) • 17 kB
Markdown
# imap-simple
**This library is no longer maintained and has been archived.**
A library providing a simpler interface for common use cases of [node-imap][], a robust imap client for node.js.
**Warning**: This library is missing a great deal of functionality from node-imap. If you have functionality you would
like to see, we're accepting pull requests!
### Examples
#### Retrieve the subject lines of all unread email
```js
var imaps = require('imap-simple');
var config = {
imap: {
user: 'your@email.address',
password: 'yourpassword',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = [
'UNSEEN'
];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: false
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
var subjects = results.map(function (res) {
return res.parts.filter(function (part) {
return part.which === 'HEADER';
})[0].body.subject[0];
});
console.log(subjects);
// =>
// [ 'Hey Chad, long time no see!',
// 'Your amazon.com monthly statement',
// 'Hacker Newsletter Issue #445' ]
});
});
});
```
#### Retrieve Body Content
```js
var imaps = require('imap-simple');
const _ = require('lodash');
var config = {
imap: {
user: 'your@email.address',
password: 'yourpassword',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = ['1:5'];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
};
return connection.search(searchCriteria, fetchOptions).then(function (messages) {
messages.forEach(function (item) {
var all = _.find(item.parts, { "which": "TEXT" })
var html = (Buffer.from(all.body, 'base64').toString('ascii'));
console.log(html)
});
});
});
});
```
#### Usage of Mailparser in combination with imap-simple
```js
var imaps = require('imap-simple');
const simpleParser = require('mailparser').simpleParser;
const _ = require('lodash');
var config = {
imap: {
user: 'your@email.address',
password: 'yourpassword',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = ['1:5'];
var fetchOptions = {
bodies: ['HEADER', 'TEXT', ''],
};
return connection.search(searchCriteria, fetchOptions).then(function (messages) {
messages.forEach(function (item) {
var all = _.find(item.parts, { "which": "" })
var id = item.attributes.uid;
var idHeader = "Imap-Id: "+id+"\r\n";
simpleParser(idHeader+all.body, (err, mail) => {
// access to the whole mail object
console.log(mail.subject)
console.log(mail.html)
});
});
});
});
});
```
#### Download all attachments from all unread email since yesterday
```js
var imaps = require('imap-simple');
var config = {
imap: {
user: 'your@email.address',
password: 'yourpassword',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
imaps.connect(config).then(function (connection) {
connection.openBox('INBOX').then(function () {
// Fetch emails from the last 24h
var delay = 24 * 3600 * 1000;
var yesterday = new Date();
yesterday.setTime(Date.now() - delay);
yesterday = yesterday.toISOString();
var searchCriteria = ['UNSEEN', ['SINCE', yesterday]];
var fetchOptions = { bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'], struct: true };
// retrieve only the headers of the messages
return connection.search(searchCriteria, fetchOptions);
}).then(function (messages) {
var attachments = [];
messages.forEach(function (message) {
var parts = imaps.getParts(message.attributes.struct);
attachments = attachments.concat(parts.filter(function (part) {
return part.disposition && part.disposition.type.toUpperCase() === 'ATTACHMENT';
}).map(function (part) {
// retrieve the attachments only of the messages with attachments
return connection.getPartData(message, part)
.then(function (partData) {
return {
filename: part.disposition.params.filename,
data: partData
};
});
}));
});
return Promise.all(attachments);
}).then(function (attachments) {
console.log(attachments);
// =>
// [ { filename: 'cats.jpg', data: Buffer() },
// { filename: 'pay-stub.pdf', data: Buffer() } ]
});
});
```
### Append a message to your drafts folder
```js
var imaps = require('imap-simple');
var config = {
imap: {
user: 'your@email.address',
password: 'yourpassword',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
imaps.connect(config).then(function (connection) {
const message = `Content-Type: text/plain
To: jhannes@gmail.com
Subject: Hello world
Hi
This is a test message
`;
connection.append(message.toString(), {mailbox: 'Drafts', flags: '\\Draft'});
});
```
### Open messages and delete them
```js
imaps.connect(config).then(function (connection) {
connection.openBox('INBOX').then(function () {
var searchCriteria = ['ALL'];
var fetchOptions = { bodies: ['TEXT'], struct: true };
return connection.search(searchCriteria, fetchOptions);
//Loop over each message
}).then(function (messages) {
let taskList = messages.map(function (message) {
return new Promise((res, rej) => {
var parts = imaps.getParts(message.attributes.struct);
parts.map(function (part) {
return connection.getPartData(message, part)
.then(function (partData) {
//Display e-mail body
if (part.disposition == null && part.encoding != "base64"){
console.log(partData);
}
//Mark message for deletion
connection.addFlags(message.attributes.uid, "\Deleted", (err) => {
if (err){
console.log('Problem marking message for deletion');
rej(err);
}
res(); //Final resolve
})
});
});
});
})
return Promise.all(taskList).then(() => {
connection.imap.closeBox(true, (err) => { //Pass in false to avoid delete-flagged messages being removed
if (err){
console.log(err);
}
});
connection.end();
});
});
});
```
### delete messages by uid
```js
imaps.connect(config).then(connection => {
return connection.openBox('INBOX')
.then(() => connection.search(['ALL'], {bodies: ['HEADER']}))
.then( messages => {
// select messages from bob
const uidsToDelete = messages
.filter( message => {
return message.parts
.filter( part => part.which === 'HEADER')[0].body.to[0] === 'bob@example.com';
})
.map(message => message.attributes.uid);
return connection.deleteMessage(uidsToDelete);
});
});
```
## API
### Exported module
- **connect**(<*object*> options, [<*function*> callback]) - *Promise* - Main entry point. Connect to an Imap server.
Upon successfully connecting to the Imap server, either calls the provided callback with signature `(err, connection)`,
or resolves the returned promise with `connection`, where `connection` is an instance of *ImapSimple*. If the connection
times out, either the callback will be called with the `err` property set to an instance of *ConnectionTimeoutError*, or
the returned promise will be rejected with the same. Valid `options` properties are:
- **imap**: Options to pass to node-imap constructor 1:1
- **connectTimeout**: Time in milliseconds to wait before giving up on a connection attempt. *(Deprecated: please
use `options.imap.authTimeout` instead)*
- **errors.ConnectionTimeoutError**(<*number*> timeout) - *ConnectionTimeoutError* - Error thrown when a connection
attempt has timed out.
- **getParts**(<*Array*> struct) - *Array* - Given the `message.attributes.struct`, retrieve a flattened array of `parts`
objects that describe the structure of the different parts of the message's body. Useful for getting a simple list to
iterate for the purposes of, for example, finding all attachments.
- **ImapSimple**(<*object*> imap) - *ImapSimple* - constructor for creating an instance of ImapSimple. Mostly used for
testing.
### ImapSimple class
- **addFlags**(<*mixed*> uid, <*string*> flag, [<*function*> callback]) - *Promise* - Adds the provided
flag(s) to the specified message(s). `uid` is the *uid* of the message you want to add the flag to or an array of
*uids*. `flag` is either a string or array of strings indicating the flags to add. When completed, either calls
the provided callback with signature `(err)`, or resolves the returned promise.
- **addMessageLabel**(<*mixed*> source, <*mixed*> label, [<*function*> callback]) - *Promise* - Adds the provided
label(s) to the specified message(s). `source` corresponds to a node-imap *MessageSource* which specifies the messages
to be moved. `label` is either a string or array of strings indicating the labels to add. When completed, either calls
the provided callback with signature `(err)`, or resolves the returned promise.
- **removeMessageLabel**(<*mixed*> source, <*mixed*> label, [<*function*> callback]) - *Promise* - Removes the provided
label(s) from the specified message(s). `source` corresponds to a node-imap *MessageSource* which specifies the messages
to be removed. `label` is either a string or array of strings indicating the labels to remove. When completed, either calls
the provided callback with signature `(err)`, or resolves the returned promise.
- **append**(<*mixed*> message, [<*object*> options], [<*function*> callback]) - *Promise* - Appends the argument
message to the currently open mailbox or another mailbox. `message` is a RFC-822 compatible MIME message. Valid `options`
are *mailbox*, *flags* and *date*. When completed, either calls the provided callback with signature `(err)`, or resolves
the returned promise.
- **delFlags**(<*mixed*> uid, <*string*> flag, [<*function*> callback]) - *Promise* - Removes the provided
flag(s) from the specified message(s). `uid` is the *uid* of the message you want to remove the flag from or an array of
*uids*. `flag` is either a string or array of strings indicating the flags to remove. When completed, either calls
the provided callback with signature `(err)`, or resolves the returned promise.
- **end**() - *undefined* - Close the connection to the imap server.
- **getBoxes**([<*function*> callback]) - *Promise* - Returns the full list of mailboxes (folders). Upon success, either
the provided callback will be called with signature `(err, boxes)`, or the returned promise will be resolved with `boxes`.
`boxes` is the exact object returned from the node-imap *getBoxes()* result.
- **getPartData**(<*object*> message, <*object*> part, [<*function*> callback]) - *Promise* - Downloads part data
(which is either part of the message body, or an attachment). Upon success, either the provided callback will be called
with signature `(err, data)`, or the returned promise will be resolved with `data`. The data will be automatically
decoded based on its encoding. If the encoding of the part is not supported, an error will occur.
- **deleteMessage**(<*mixed*> uid, [<*function*> callback]) - *Promise* - Deletes the specified
message(s). `uid` is the *uid* of the message you want to add the flag to or an array of *uids*.
When completed, either calls the provided callback with signature `(err)`, or resolves the returned promise.
- **moveMessage**(<*mixed*> source, <*string*> boxName, [<*function*> callback]) - *Promise* - Moves the specified
message(s) in the currently open mailbox to another mailbox. `source` corresponds to a node-imap *MessageSource* which
specifies the messages to be moved. When completed, either calls the provided callback with signature `(err)`, or
resolves the returned promise.
- **openBox**(<*string*> boxName, [<*function*> callback]) - *Promise* - Open a mailbox, calling the provided callback
with signature `(err, boxName)`, or resolves the returned promise with `boxName`.
- **closeBox**(<*boolean*> [autoExpunge = true], [<*function*> callback]) - *Promise* - Close a mailbox, calling the provided callback
with signature `(err)`, or resolves the returned promise. If autoExpunge is true, any messages marked as Deleted in the currently
open mailbox will be removed.
- **addBox**(<*string*> boxName, [<*function*> callback]) - *Promise* - Create a mailbox, calling the provided callback
with signature `(err, boxName)`, or resolves the returned promise with `boxName`.
- **delBox**(<*string*> boxName, [<*function*> callback]) - *Promise* - Delete a mailbox, calling the provided callback
with signature `(err, boxName)`, or resolves the returned promise with `boxName`.
- **search**(<*object*> searchCriteria, [<*object*> fetchOptions], [<*function*> callback]) - *Promise* - Search for and
retrieve mail in the currently open mailbox. The search is performed based on the provided `searchCriteria`, which is
the exact same format as [node-imap][] requires. All results will be subsequently downloaded, according to the options
provided by `fetchOptions`, which are also identical to those passed to `fetch` of [node-imap][]. Upon a successful
search+fetch operation, either the provided callback will be called with signature `(err, results)`, or the returned
promise will be resolved with `results`. The format of `results` is detailed below. See node-imap's *ImapMessage*
signature for information about `attributes`, `which`, `size`, and `body`. For any message part that is a `HEADER`, the
body is automatically parsed into an object.
```js
// [{
// attributes: object,
// parts: [ { which: string, size: number, body: string }, ... ]
// }, ...]
```
## Server events
Functions to listen to server events are configured in the configuration object that is passed to the `connect` function.
ImapSimple only implements a subset of the server event functions that *node-imap* supports, [see here](https://github.com/mscdex/node-imap#connection-events),
which are `mail`, `expunge` and `update`. Add them to the configuration object as follows:
```
var config = {
imap: {
...
},
onmail: function (numNewMail) {
...
},
onexpunge: function (seqno) {
...
},
onupdate: function (seqno, info) {
...
}
};
```
For more information [see here](https://github.com/mscdex/node-imap#connection-events).
## Contributing
Pull requests welcome! This project really needs tests, so those would be very welcome. If you have a use case you want
supported, please feel free to add, but be sure to follow the patterns established thus far, mostly:
- support promises **AND** callbacks
- make your api as simple as possible
- don't worry about exposing implementation details of [node-imap][] when needed
This project is **OPEN** open source. See [CONTRIBUTING.md](CONTRIBUTING.md) for more details about contributing.
## Semver
This project follows [semver](http://semver.org/). Namely:
- new MAJOR versions when incompatible API changes are made,
- new MINOR versions for backwards-compatible feature additions,
- new PATCH versions for backwards-compatible bug fixes
## License
[MIT](LICENSE-MIT)
[node-imap]: https://github.com/mscdex/node-imap