Invoices, signed contracts, and reports often arrive as Gmail attachments that quietly pile up in an inbox instead of living in a shared Drive folder where a team can find them.
This script searches for matching messages, walks through every attachment on every message, and saves each file into a Drive folder named for the current month.
To avoid saving the same attachment twice on a re-run, the script labels each processed message and skips file types like calendar invites that are rarely worth keeping.
By the end you will have a repeatable pipeline that turns an email attachment habit into an organized, searchable Drive archive with no manual downloading involved.
Need this built? Hire a Google Apps Script developer →
Sheet / project setup
| Resource | Name / value | Purpose |
|---|---|---|
| Gmail label | has-attachments | Source thread filter |
| Drive folder | Email Attachments | Parent folder for saved blobs |
| Processed marker | saved-to-drive label | Prevents re-saving |
What It Does
The script searches Gmail for messages matching a label, loops through their attachments, and writes each file into a Drive folder named after the current month, creating that folder if it does not exist yet.
It skips attachments smaller than 1 KB and calendar invite files, since those are almost always inline images or scheduling noise rather than documents worth archiving.
Prerequisites
Create a top-level Email Attachments folder in Drive and note its ID, since the script creates month subfolders underneath that fixed parent rather than scattering folders across your whole Drive.
Apply a Gmail label to the messages you want archived, either manually while testing or through a Gmail filter once you know the automation works as expected.
Walkthrough
Paste the saveAttachmentsToDrive function into the script editor, set PARENT_FOLDER_ID to the folder ID from the Drive URL, and update LABEL_NAME to match your Gmail label.
Run the function once manually and grant the Gmail and Drive scopes, then open Drive and confirm a month-named subfolder appeared containing the expected files.
Add a time-driven trigger every 30 minutes once you are satisfied attachments are landing in the right place with the right names.
Edge Cases
Messages with no attachments at all are labeled as processed too, so the script does not waste time re-scanning them on every subsequent run.
Two attachments with identical filenames from different messages are both kept, since DriveApp allows duplicate names in the same folder and the script does not attempt to merge them.
Testing
Send yourself a test email with two attached files, one above and one below the 1 KB threshold, and confirm only the larger file is saved to Drive.
Re-run the function immediately after and verify no new files are created, which confirms the done-label check is correctly skipping already-processed messages.
Hardening
Wrap each attachment's DriveApp.createFile call in its own try/catch so one corrupted or oversized attachment cannot stop the rest of the batch from being saved.
Track total bytes saved per run in a log sheet so you notice early if attachment volume is trending toward your Drive storage limit.
Variations
Instead of grouping by month, group folders by sender domain when you need an archive organized by vendor rather than by time.
Pair this script with the folder-structure tutorial to file attachments directly into an existing Year/Month/Project hierarchy instead of a flat month folder.
saveAttachmentsToDrive.gs
saveAttachmentsToDrive finds a shared month folder or creates it, then copies every attachment larger than 1 KB and not a calendar invite from matching Gmail threads into that folder.
// Save matching Gmail attachments into a dated Drive folder
function saveAttachmentsToDrive() {
var LABEL_NAME = 'has-attachments';
var DONE_LABEL = 'attachments-saved';
var PARENT_FOLDER_ID = 'REPLACE_WITH_FOLDER_ID';
var parent = DriveApp.getFolderById(PARENT_FOLDER_ID);
var monthName = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM');
var folders = parent.getFoldersByName(monthName);
var monthFolder = folders.hasNext() ? folders.next() : parent.createFolder(monthName);
var doneLabel = GmailApp.getUserLabelByName(DONE_LABEL) || GmailApp.createLabel(DONE_LABEL);
var threads = GmailApp.search('label:' + LABEL_NAME + ' -label:' + DONE_LABEL, 0, 30);
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
for (var j = 0; j < messages.length; j++) {
var attachments = messages[j].getAttachments();
for (var k = 0; k < attachments.length; k++) {
var file = attachments[k];
if (file.getSize() > 1024 && file.getContentType() !== 'text/calendar') {
monthFolder.createFile(file);
}
}
}
threads[i].addLabel(doneLabel);
}
}- Line 8: Formatting today's date as yyyy-MM gives every attachment saved this month a shared, sortable folder name.
- Line 10: Checking hasNext before creating a folder means the script reuses this month's folder on every run instead of making a new one each time.
- Line 18: getAttachments returns every file on a single message, which the inner loop then filters one at a time.
- Line 21: Filtering out small files and calendar invites keeps inline signature images and scheduling noise out of the archive.
- Line 22: createFile on the month folder is what actually copies the attachment blob into Drive as a standalone file.
- Line 26: Labeling the whole thread as saved, even if some of its attachments were filtered out, avoids rescanning it on the next run.
Deploy this example
- 01
Open Apps Script
In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.
- 02
Paste and save
Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.
- 03
Authorize once
Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.
- 04
Add the trigger
Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.
Before you run: save gmail attachments to drive
- 1Parent 'Email Attachments' folder created with its Drive ID copied
- 2Gmail label applied to messages containing relevant attachments
- 3Size and content-type filters reviewed for your use case
- 4Function run manually and Gmail/Drive scopes granted
- 5Month subfolder confirmed to appear correctly
- 6Re-run tested to confirm no duplicate saves
- 7Time-driven trigger scheduled
Frequently asked questions
Most signature images are small enough to be filtered out by the 1 KB size threshold, but you can raise or remove that threshold if needed.
They are still labeled as processed so the script does not keep rescanning them, even though nothing is saved to Drive for those messages.
No, DriveApp allows duplicate filenames in the same folder, so both files are kept side by side rather than one overwriting the other.
Swap the month-folder logic for a lookup based on the sender's domain, following the same getOrCreateFolder pattern shown in the folder-structure tutorial.
It only ever handles whatever Gmail already accepted as an attachment, so there is no additional size limit imposed by this script beyond Gmail's own.
DriveApp.getFolderById throws an error immediately, which is a clear signal to double check the ID copied from the folder's URL.