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

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