How to Read and Save Email Attachments Using node-imap in Node.js: Step-by-Step Guide with Code Example
In today’s digital world, automating email processing is a common requirement for applications ranging from customer support tools to data integration pipelines. A frequent task in such automation is reading emails and extracting attachments (e.g., invoices, reports, or user uploads) for storage or further processing.
If you’re working with Node.js, the node-imap library is a powerful tool to interact with IMAP (Internet Message Access Protocol) servers—used by most email providers (Gmail, Outlook, Yahoo, etc.) to retrieve emails. Combined with mailparser (a MIME email parser), you can easily parse raw email data and extract attachments.
This guide will walk you through setting up a Node.js project, connecting to an IMAP server, fetching emails, parsing content, and saving attachments step-by-step. By the end, you’ll have a working script to automate email attachment extraction.
Table of Contents#
- Prerequisites
- Setting Up the Project
- Understanding IMAP Configuration
- Connecting to the IMAP Server
- Selecting a Mailbox
- Searching for Emails
- Fetching and Parsing Emails
- Extracting and Saving Attachments
- Complete Code Example
- Troubleshooting Common Issues
- Conclusion
- References
Prerequisites#
Before you begin, ensure you have the following:
- Node.js & npm: Installed on your system (v14+ recommended). Download from nodejs.org.
- Email Account with IMAP Access: Most email providers (Gmail, Outlook, Yahoo) support IMAP. For Gmail, enable "Less secure app access" (not recommended) or use an App Password (if 2FA is enabled).
- Code Editor: e.g., VS Code.
- Basic Knowledge: Familiarity with Node.js, async/await, and file system operations.
Setting Up the Project#
First, create a new project directory and initialize it with npm:
mkdir imap-email-attachments
cd imap-email-attachments
npm init -yInstall the required libraries:
node-imap: To interact with IMAP servers.mailparser: To parse raw email data into structured objects (including attachments).fsandpath: Built-in modules for file system operations (no need to install).
npm install node-imap mailparserUnderstanding IMAP Configuration#
To connect to an IMAP server, you need to configure server details. Here’s a breakdown of common settings for popular email providers:
| Provider | IMAP Host | Port | TLS |
|---|---|---|---|
| Gmail | imap.gmail.com | 993 | Yes |
| Outlook | imap-mail.outlook.com | 993 | Yes |
| Yahoo | imap.mail.yahoo.com | 993 | Yes |
For Gmail with 2FA enabled, use an App Password (generate one in Google Account > Security > App Passwords).
Connecting to the IMAP Server#
Let’s start by creating a Node.js script (index.js) and configuring the IMAP connection.
Step 1: Import Dependencies#
const Imap = require('node-imap');
const { simpleParser } = require('mailparser');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util'); // To convert callbacks to promisesStep 2: Configure IMAP#
Define your IMAP settings in a configuration object:
const imapConfig = {
user: '[email protected]', // Replace with your email
password: 'your-app-password', // Replace with App Password (Gmail 2FA) or regular password
host: 'imap.gmail.com', // Use provider-specific host
port: 993,
tls: true, // Enable TLS encryption
tlsOptions: { rejectUnauthorized: false } // Disable if server uses self-signed cert (not recommended for production)
};Connecting to the IMAP Server#
Create an Imap instance with the config and connect to the server. We’ll use promisify to convert callback-based methods to promises for cleaner async/await syntax.
// Initialize IMAP client
const imap = new Imap(imapConfig);
// Promisify IMAP methods for async/await
const openBoxAsync = promisify(imap.openBox).bind(imap);
const searchAsync = promisify(imap.search).bind(imap);Handle connection events:
ready: Triggered when the client successfully connects.error: Triggered on connection errors.end: Triggered when the connection closes.
imap.on('ready', async () => {
console.log('Connected to IMAP server');
try {
// Your email processing logic here
} catch (err) {
console.error('Error processing emails:', err);
} finally {
imap.end(); // Close connection when done
}
});
imap.on('error', (err) => {
console.error('IMAP error:', err);
});
imap.on('end', () => {
console.log('Connection closed');
});
// Start connecting
imap.connect();Selecting the Mailbox#
Once connected, you need to open a mailbox (e.g., INBOX) to access emails. Use openBoxAsync (promisified openBox method):
// Inside the 'ready' event handler
const mailbox = 'INBOX'; // or 'Sent', 'Drafts', etc.
await openBoxAsync(mailbox);
console.log(`Opened mailbox: ${mailbox}`);Searching for Emails#
Use searchAsync to filter emails based on criteria like unread status, date, subject, or sender. Common search criteria:
'UNSEEN': Unread emails.['FROM', '[email protected]']: Emails from a specific sender.['SINCE', 'May 20, 2024']: Emails since a date.['SUBJECT', 'Invoice']: Emails with "Invoice" in the subject.
Example: Search for all unread emails:
// Inside the 'ready' event handler
const searchCriteria = ['UNSEEN']; // Search for unread emails
const emailIds = await searchAsync(searchCriteria);
console.log(`Found ${emailIds.length} emails matching criteria`);
if (emailIds.length === 0) {
console.log('No emails to process');
return;
}Fetching and Parsing Emails#
After finding email IDs, fetch the emails and parse their content using mailparser. The fetch method retrieves email data, and simpleParser converts raw MIME data into a structured object.
Fetch Emails#
Use imap.fetch() to retrieve emails by their IDs. Specify bodies: '' to fetch the full email content:
// Inside the 'ready' event handler
const fetch = imap.fetch(emailIds, { bodies: '' }); // Fetch full email bodyProcess Each Email#
Listen to the message event to process individual emails. Pipe the email stream into simpleParser for parsing:
fetch.on('message', (msg, seqno) => {
console.log(`Processing email ${seqno}`);
// Parse the email with mailparser
msg.on('body', (stream) => {
simpleParser(stream)
.then(parsedEmail => {
console.log(`Parsed email: ${parsedEmail.subject}`);
// Extract and save attachments here (see next section)
})
.catch(err => {
console.error(`Error parsing email ${seqno}:`, err);
});
});
msg.on('end', () => {
console.log(`Finished processing email ${seqno}`);
});
});
fetch.on('error', (err) => {
console.error('Fetch error:', err);
});
fetch.on('end', () => {
console.log('All emails fetched');
});Extracting and Saving Attachments#
The parsedEmail object from mailparser includes an attachments array. Each attachment has:
filename: Name of the attachment.content: Buffer containing the file data.contentType: MIME type (e.g.,application/pdf).
Step 1: Create an Attachments Directory#
Save attachments to a dedicated folder (e.g., ./attachments). Create the folder if it doesn’t exist:
const attachmentsDir = path.join(__dirname, 'attachments');
if (!fs.existsSync(attachmentsDir)) {
fs.mkdirSync(attachmentsDir, { recursive: true });
console.log(`Created attachments directory: ${attachmentsDir}`);
}Step 2: Save Attachments#
Loop through the attachments array and save each file using fs.writeFile:
// Inside simpleParser's .then() block
if (parsedEmail.attachments && parsedEmail.attachments.length > 0) {
console.log(`Found ${parsedEmail.attachments.length} attachments`);
parsedEmail.attachments.forEach(attachment => {
const filePath = path.join(attachmentsDir, attachment.filename);
// Save attachment buffer to file
fs.writeFile(filePath, attachment.content, (err) => {
if (err) {
console.error(`Failed to save ${attachment.filename}:`, err);
} else {
console.log(`Saved attachment: ${filePath}`);
}
});
});
} else {
console.log('No attachments found');
}Complete Code Example#
Here’s the full script combining all steps:
const Imap = require('node-imap');
const { simpleParser } = require('mailparser');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
// IMAP Configuration (Replace with your details)
const imapConfig = {
user: '[email protected]',
password: 'your-app-password', // Use App Password for Gmail 2FA
host: 'imap.gmail.com',
port: 993,
tls: true,
tlsOptions: { rejectUnauthorized: false }
};
// Initialize IMAP client
const imap = new Imap(imapConfig);
// Promisify IMAP methods
const openBoxAsync = promisify(imap.openBox).bind(imap);
const searchAsync = promisify(imap.search).bind(imap);
// Create attachments directory if it doesn't exist
const attachmentsDir = path.join(__dirname, 'attachments');
if (!fs.existsSync(attachmentsDir)) {
fs.mkdirSync(attachmentsDir, { recursive: true });
console.log(`Created attachments directory: ${attachmentsDir}`);
}
// Process emails
const processEmails = async () => {
try {
// Open INBOX mailbox
await openBoxAsync('INBOX');
console.log('Opened INBOX');
// Search for unread emails (customize criteria as needed)
const searchCriteria = ['UNSEEN']; // e.g., ['SINCE', 'May 20, 2024']
const emailIds = await searchAsync(searchCriteria);
console.log(`Found ${emailIds.length} emails`);
if (emailIds.length === 0) return;
// Fetch emails
const fetch = imap.fetch(emailIds, { bodies: '' });
fetch.on('message', (msg, seqno) => {
console.log(`Processing email ${seqno}`);
msg.on('body', (stream) => {
simpleParser(stream)
.then(parsedEmail => {
console.log(`\nEmail Subject: ${parsedEmail.subject}`);
console.log(`From: ${parsedEmail.from.text}`);
// Save attachments
if (parsedEmail.attachments && parsedEmail.attachments.length > 0) {
parsedEmail.attachments.forEach(attachment => {
const filePath = path.join(attachmentsDir, attachment.filename);
fs.writeFile(filePath, attachment.content, (err) => {
if (err) {
console.error(`❌ Failed to save ${attachment.filename}:`, err);
} else {
console.log(`✅ Saved attachment: ${filePath}`);
}
});
});
} else {
console.log('No attachments found');
}
})
.catch(err => {
console.error(`Error parsing email ${seqno}:`, err);
});
});
msg.on('end', () => {
console.log(`Processed email ${seqno}`);
});
});
fetch.on('error', (err) => {
console.error('Fetch error:', err);
});
fetch.on('end', () => {
console.log('All emails processed');
});
} catch (err) {
console.error('Error processing emails:', err);
} finally {
imap.end();
}
};
// IMAP Event Listeners
imap.on('ready', processEmails);
imap.on('error', (err) => console.error('IMAP Error:', err));
imap.on('end', () => console.log('Connection closed'));
// Start connection
imap.connect();Troubleshooting Common Issues#
1. Authentication Failed#
- Gmail 2FA: Use an App Password instead of your regular password.
- Less Secure Apps: For non-2FA accounts, enable "Less secure app access" (not recommended for production).
2. Connection Refused#
- Verify the
hostandport(use 993 with TLS for most providers). - Check firewall settings (ensure port 993 is not blocked).
3. Attachments Not Saving#
- Ensure the
attachmentsdirectory has write permissions. - Check if
parsedEmail.attachmentsis not empty (some emails may have no attachments).
4. Large Attachments#
- Use streams instead of buffers for large files to avoid memory issues.
Conclusion#
You now know how to connect to an IMAP server, fetch emails, parse them, and save attachments using node-imap and mailparser in Node.js. This script can be extended to:
- Filter emails by date, sender, or subject.
- Mark emails as "read" after processing.
- Handle specific file types (e.g., only PDFs).
- Upload attachments to cloud storage (AWS S3, Google Drive).