Forward attachments to Drive and reply with the link
Strip large attachments out of incoming threads, save them to Drive, and reply with the link.
Published Jun 9, 2026
Large attachments do not belong in a mailbox. A 20MB mockup PDF sits in the thread forever, counts against the account’s storage, and is impossible to find again three weeks later when someone asks “where’s that file?”. The file should live in Drive, organised by client, and the email should just point at it.
When a client sends a heavy attachment to Northwind, this script saves it to
Drive › clients/{name}, replies to the thread with a link, and labels the
thread so it is never processed twice. It runs on a short timer, so files are
offloaded within minutes of arriving — the sender gets a tidy link reply and the
mailbox stays light.
What you’ll need
- A
clients/Drive folder to act as the root for per-client subfolders. The script creates the subfolders itself, named after each sender’s domain. - Nothing else — the
attachments/offloadedGmail label is created on first run if it doesn’t already exist.
The script
// The Drive folder that holds one subfolder per client.
const CLIENTS_ROOT = '1abcClientsRootId';
// Only attachments at or above this size are offloaded. Smaller files
// stay in the thread where they're harmless.
const MIN_SIZE = 5 * 1024 * 1024; // 5MB
// The label marking a thread as already processed.
const DONE_LABEL = 'attachments/offloaded';
/**
* Finds recent threads with large attachments, saves those attachments
* to a per-client Drive folder, and replies with the links.
*/
function offloadAttachments() {
// 1. Search for recent threads with attachments not yet offloaded.
const threads = GmailApp.search(
`has:attachment newer_than:1d -label:${DONE_LABEL}`);
if (!threads.length) {
Logger.log('No new threads with attachments — nothing to do.');
return;
}
// Find or create the label that marks a thread as processed.
const done = GmailApp.getUserLabelByName(DONE_LABEL)
|| GmailApp.createLabel(DONE_LABEL);
let offloaded = 0;
for (const t of threads) {
// 2. Look at the most recent message in the thread.
const msg = t.getMessages().slice(-1)[0];
// 3. Keep only attachments at or above the size threshold.
const big = msg.getAttachments()
.filter((a) => a.getBytes().length >= MIN_SIZE);
if (big.length === 0) continue;
// 4. Resolve (or create) the Drive folder for this sender.
const folder = clientFolder(msg.getFrom());
// 5. Save each big attachment and collect a name-and-link line.
const links = big.map((a) => {
const file = folder.createFile(a);
return `${a.getName()}: ${file.getUrl()}`;
});
// 6. Reply to the thread with the links, then label it done.
t.reply(`Saved attachments to Drive:\n\n${links.join('\n')}\n\n— Northwind`);
t.addLabel(done);
offloaded += big.length;
}
Logger.log(`Offloaded ${offloaded} attachment(s).`);
}
/**
* Returns the Drive folder for a sender, keyed by the first segment of
* their email domain. Creates the folder on first sight of a client.
*/
function clientFolder(from) {
// Pull the domain from the From header; "acme.com" -> "acme".
const domain = (from.match(/@([\w.-]+)/) || [, 'misc'])[1].split('.')[0];
const root = DriveApp.getFolderById(CLIENTS_ROOT);
const it = root.getFoldersByName(domain);
return it.hasNext() ? it.next() : root.createFolder(domain);
}
How it works
offloadAttachmentsruns a Gmail search for threads from the last day that carry an attachment and are not yet labelledattachments/offloaded. If nothing matches, it stops.- It finds or creates the
attachments/offloadedlabel, then loops over each matching thread, looking only at the most recent message — the one that most likely carries the new attachment. - It filters that message’s attachments down to ones at or above
MIN_SIZE(5MB). Small files are left in the thread, where they cost nothing. - For each thread with a big attachment,
clientFolderresolves the right Drive subfolder from the sender’s email domain — for example, anything from@acme.comlands in aacmefolder. If the folder doesn’t exist yet, it is created. - Each large attachment is saved with
folder.createFile, and a line pairing the file name with its Drive URL is collected. - The script replies to the thread with all the links and adds the
donelabel, so the same thread is never processed again on a later run.
Example run
A client at [email protected] emails Northwind with a 20MB file
homepage-v3.pdf attached and a 40KB notes.txt.
On the next run the script picks up the thread, keeps only homepage-v3.pdf
(the .txt is under 5MB), and saves it to clients/acme/. It then posts a
reply on the thread:
Saved attachments to Drive:
homepage-v3.pdf: https://drive.google.com/file/d/.../view
— Northwind
The thread gets the attachments/offloaded label, and the log records
Offloaded 1 attachment(s). A later run skips this thread entirely.
Trigger it
- In the Apps Script editor, open Triggers and click Add trigger.
- Function:
offloadAttachments. Event source: time-based. Type: minutes timer, every 15 minutes. - Save. Attachments are now offloaded within a quarter of an hour of arriving.
Watch out for
- The script only inspects the last message in each thread. If a heavy attachment arrives on an earlier message in a long thread, it is missed — acceptable for fresh client threads, but worth knowing.
clientFolderkeys folders off the domain’s first segment. Two unrelated clients on the same provider domain (for example, two@gmail.comsenders) would share one folder. For a B2B inbox where everyone uses their own domain this is fine; for mixed senders, key off the full address instead.a.getBytes().lengthloads the entire attachment into memory to measure it. For very large files this is slow and can press against script memory limits. If you process big files often, checka.getSize()where available rather than reading the bytes.- The
newer_than:1dfilter means a run that fails for more than a day can let threads age out of the search window unprocessed. The label still prevents duplicates, but widen the window if the trigger has been unreliable. - The script replies to the thread, which notifies the sender. If you would
rather offload silently, drop the
t.replycall and keep only the label.
Related
Auto-forward receipts to your accountant
Route anything that looks like a receipt to a fixed address on a monthly schedule.
Updated Jun 16, 2026
Auto-file quotes and proposals you receive
Detect inbound proposal PDFs and store them in Drive by client.
Updated Jun 13, 2026
Parse bank-alert emails into an expense ledger
Convert transaction alerts from Northwind's bank into categorised spend rows automatically.
Updated Apr 28, 2026
Convert long email threads into a summary note
Collapse a thread's history into a Doc for handover — perfect for client transitions or vacation cover.
Updated Jun 6, 2026
Pull event RSVPs from emails into a Sheet
Parse yes/no replies to event invites and tally attendance automatically.
Updated Jun 2, 2026