Skip to content

Instantly share code, notes, and snippets.

@papnkukn
Last active December 4, 2023 16:35
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save papnkukn/faa7f22ab2b8a65d7e1e4308fa4a5b42 to your computer and use it in GitHub Desktop.
Save papnkukn/faa7f22ab2b8a65d7e1e4308fa4a5b42 to your computer and use it in GitHub Desktop.
Backup Gmail / Download all Gmail e-mail messages using REST API and Node.js
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const {google} = require('googleapis');
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
const TOKEN_PATH = 'token.json';
const OUTPUT_FOLDER = './output';
const METADATA_FILE = 'metadata.json';
//Gmail API credentials from https://developers.google.com/gmail/api/quickstart/nodejs
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
authorize(JSON.parse(content), function(auth) {
downloadAll(auth);
});
});
//Authorize using OAuth2
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
//Request new OAuth token
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
//Get a specific e-mail message: https://developers.google.com/gmail/api/v1/reference/users/messages/get
function getMessage(auth, id) {
return new Promise((resolve, reject) => {
if (!auth) return reject("Authentication data is missing!");
if (!id) return reject("Message id is missing!");
const gmail = google.gmail({version: 'v1', auth});
const query = {
userId: 'me',
id: id,
format: 'raw'
};
gmail.users.messages.get(query, (err, res) => {
if (err) return reject("The API returned an error: " + err);
if (res.data.raw) {
res.data.eml = Buffer.from(res.data.raw, 'base64').toString("utf-8");
}
resolve(res.data);
});
});
}
//List e-mail messages, use pageToken for next page
function listMessages(auth, pageToken) {
return new Promise((resolve, reject) => {
if (!auth) return reject("Authentication data is missing!");
var query = { userId: 'me' };
if (pageToken) {
query.pageToken = pageToken;
}
//Adding search query: https://support.google.com/mail/answer/7190?hl=en
//query.q = "before:yyyy/mm/dd after:yyyy/mm/dd"
const gmail = google.gmail({version: 'v1', auth});
gmail.users.messages.list(query, (err, res) => {
if (err) return reject('The API returned an error: ' + err);
resolve(res.data);
});
});
}
//Download all messages for the authenticated Gmail user
async function downloadAll(auth) {
var page = 0, counter = 0;
var pageToken = null;
var metadata = [ ];
//Output folder
var folder = OUTPUT_FOLDER;
if (!fs.existsSync(folder)) {
throw new Error("Output folder does not exist!");
}
if (fs.readdirSync(folder).length > 0) {
throw new Error("Output folder should be empty!");
}
do {
//Print page number
page++;
console.log(pageToken ? "Page #" + page + ": " + pageToken : "Page #1");
//Get list of messages
var res = await listMessages(auth, pageToken);
pageToken = res.nextPageToken;
var messages = res.messages || [ ];
for (let message of messages) {
//Print message number
counter++;
console.log("Downloading #" + counter + ": " + message.id);
//Get message data
var msg = await getMessage(auth, message.id);
//Save message to a file
var file = path.join(folder, message.id + ".eml");
fs.writeFileSync(file, msg.eml);
//Store message metadata
delete msg.raw;
delete msg.eml;
metadata.push(msg);
}
}
while (pageToken);
//Save metadata to a file
console.log("Saving metadata...");
var file = path.join(folder, METADATA_FILE);
fs.writeFileSync(file, JSON.stringify(metadata, " ", 2));
console.log("Done!");
}
@papnkukn
Copy link
Author

Requirements

  • Node.js 7.6+
  • Gmail access

Instructions

  1. Download the script file download.js
  2. Run npm install googleapis@39 in the same directory.
  3. Visit Gmail API Quickstart page.
  4. Under the Step 1 chapter click the Enable the Gmail API button to generate credentials.json. Save the generated file in the same directory as the download.js script file.
  5. Create output folder in the same directory.
  6. Run the script: node download.js
  7. Take a coffee.

@papnkukn
Copy link
Author

papnkukn commented Oct 13, 2019

Console output goes

...
Downloading #3355: 119fc4a10ac8ef9d
Downloading #3356: 112af454df3b1030
Saving metadata...
Done!

and stores each message as a file

...
./output/119fc4a10ac8ef9d.eml
./output/112af454df3b1030.eml

including

./output/metadata.json

which tells whether a message is from inbox, sent folder, label, thread, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment