Use the library
The npm package that installs the CLI is also a Node.js library. The CLI calls the same functions, so anything a command can do, your code can do.
npm install @finopsbricks/fob-email
It's an ES module and needs Node.js 22.13 or later.
A client per mailbox#
import { fobEmail } from '@finopsbricks/fob-email';
const mbox = fobEmail('billing');
try {
const { data } = await mbox.emails.list({ folder: 'INBOX', unseenOnly: true, limit: 20 });
for (const envelope of data.filter((e) => e.hasAttachment)) {
const files = await mbox.emails.download(envelope.id, { folder: 'INBOX' });
// files: [{ filename, contentType, size, content: Buffer }]
await mbox.emails.move(envelope.id, 'Invoices/2026', { folder: 'INBOX' });
}
} finally {
await mbox.close();
}
The client connects on first use. Always call close(), as above, or your process keeps the connection open.
Credentials#
fobEmail() takes an account in one of two forms:
| You pass | Where the credentials come from |
|---|---|
A name, such as 'billing', or nothing | FOB_EMAIL_ACCOUNTS, then ~/.fob/fob-email/config.yml, in the same order as the CLI |
An object: { imap: { host, port, user, pass, tls }, smtp: { host, port, user, pass, secure } } | Only that object. Nothing is read from the environment or config file |
In a worker, pass the object built from your own secret store. That keeps the library from depending on any file on the machine.
What's on the client#
| Namespace | Methods |
|---|---|
emails | list({ folder, unseenOnly, limit }), search({ folder, criteria, limit }), get(id, { folder }), download(id, { folder }), mark(id, seen, { folder }), move(id, to, { folder }), delete(id, { folder }), send(message) |
threads | list({ folder, limit }), show(id, { folder }) |
drafts | list(), create(message), edit(id, message), delete(id), send(id) |
folders | list(), create(path), rename(path, to), delete(path) |
sync | run({ folder, full, limit }), runAll({ full, limit }), read({ folder, unseen, limit, from, subject, since }), status({ folder }), clear({ folder }) |
listandsearchreturn{ data, uidValidity, folder }, wheredatais the array of messages described in JSON shapes.searchtakes an imapflow search object ascriteria, for example{ from: '[email protected]', since: new Date('2026-09-01') }.- A
messageforsendand drafts is{ to: [...], cc, bcc, subject, text, attachments: [{ path, filename }] }. sync.read()is synchronous and throws if the folder was never synced.
The same caveats as the CLI apply: IDs belong to a folder, and delete is permanent.
Helpers#
filterEmails(envelopes, { from, to, subject, hasAttachment, seen })filters an array of messages in memory, likeemails filter.listEmails({ account, ...opts })andreadEmail({ account, id, folder })connect, do one thing and close.getProfile(account)signs in and returns{ address, provider, threadStrategy, folders }.