// 1. Retrieve our current position from the script's property store
const scriptProperties = PropertiesService.getScriptProperties();
let startIndex = parseInt(scriptProperties.getProperty('START_INDEX')) || 0;
// Locate or create the destination folder
let folder;
const folders = DriveApp.getFoldersByName(FOLDER_NAME);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(FOLDER_NAME);
}
// 2. Process batches in a loop
// Updated search query to ignore USPS and non-primary tabs
const searchQuery = 'has:attachment -from:usps.gov -category:promotions -category:updates -category:social (filename:jpg OR filename:jpeg OR filename:png OR filename:zip)';
while (true) {
const threads = GmailApp.search(searchQuery, startIndex, BATCH_SIZE);
// If the search returns empty, we've hit the end of the inbox
if (threads.length === 0) {
console.log("Extraction completely finished.");
scriptProperties.deleteProperty('START_INDEX');
deleteTriggers(); // Clean up so it doesn't keep running
return;
}
for (let i = 0; i < threads.length; i++) {
const messages = threads[i].getMessages();
for (let j = 0; j < messages.length; j++) {
const attachments = messages[j].getAttachments();
for (let k = 0; k < attachments.length; k++) {
const attachment = attachments[k];
const name = attachment.getName().toLowerCase();
const type = attachment.getContentType();
const size = attachment.getSize();
// Apply size threshold (102,400 bytes = 100KB) and file type filter
if (size > 102400) {
if (type.includes('image/') || name.endsWith('.zip')) {
folder.createFile(attachment);
}
}
}
}
}
// Increment our starting index for the next batch
startIndex += BATCH_SIZE;
// 3. The Stopwatch Check
if (Date.now() - START_TIME > TIME_LIMIT) {
console.log(`Approaching timeout at index ${startIndex}. Saving state and passing the baton...`);
scriptProperties.setProperty('START_INDEX', startIndex.toString());
scheduleNextRun();
return; // Exit to avoid the hard timeout exception
}
}
}
function scheduleNextRun() {
deleteTriggers(); // Prevent duplicate overlapping triggers
ScriptApp.newTrigger("autoExtractGmailMedia")
.timeBased()
.after(60 * 1000) // Fire a new execution 1 minute from now
.create();
}
function deleteTriggers() {
const triggers = ScriptApp.getProjectTriggers();
for (let i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction() === "autoExtractGmailMedia") {
ScriptApp.deleteTrigger(triggers[i]);
}
}
}
Clever. But Gmail and Google Drive share the exact same 15 gb storage quota. If a user has 10 gigs of photos in their Gmail and runs this script, it'll create another 10 gb of files in their Drive.
Instead of saving space, this script doubles their storage usage to 20 gb, breaking through the 15 gb free tier and forcing them to buy a Google One subscription.
That’s one of the reasons this desktop app exists, it streams the files straight to your hard drive so you can avoid this
I'm stuck enough in the gecosystem that the storage is of small worry there but good point. It's a cute idea for the main topic - I saw some pictures I'd forgotten about for 20 years so that was fun.
function autoExtractGmailMedia() { const START_TIME = Date.now(); const TIME_LIMIT = 1000 * 60 * 5; // 5 minutes (leaves a 1-minute safety buffer) const BATCH_SIZE = 50; const FOLDER_NAME = "Gmail_Media_Export";
}function scheduleNextRun() { deleteTriggers(); // Prevent duplicate overlapping triggers ScriptApp.newTrigger("autoExtractGmailMedia") .timeBased() .after(60 * 1000) // Fire a new execution 1 minute from now .create(); }
function deleteTriggers() { const triggers = ScriptApp.getProjectTriggers(); for (let i = 0; i < triggers.length; i++) { if (triggers[i].getHandlerFunction() === "autoExtractGmailMedia") { ScriptApp.deleteTrigger(triggers[i]); } } }