Last active
September 19, 2024 21:24
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!"); | |
} |
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
Requirements
Instructions
download.js
npm install googleapis@39
in the same directory.credentials.json
. Save the generated file in the same directory as thedownload.js
script file.output
folder in the same directory.node download.js