Auto-file quotes and proposals you receive
Detect inbound proposal PDFs and store them in Drive by client.
Published Jun 13, 2026
When a vendor sends Northwind a quote, the PDF should land in
Drive › quotes/{vendor}/ automatically. In practice it sits in the inbox,
gets half-noticed, and is impossible to find three weeks later when someone
asks “what did the printers actually quote us?”.
This script watches recent mail for quotes and proposals. When a thread looks
like a quote and carries a PDF, it saves the PDF into a per-vendor folder under
a single quotes/ root, named from the sender’s domain. No dragging
attachments around — every quote files itself the moment it arrives.
What you’ll need
- A
quotes/root folder in Drive for the per-vendor sub-folders. You need its folder ID. - Mail that actually arrives in Gmail — the script searches recent threads with attachments, so a forwarding rule that strips attachments will defeat it.
- Nothing else — the script creates the
quotes/filedlabel and each vendor sub-folder itself.
The script
// Root Drive folder; per-vendor sub-folders are created inside it.
const QUOTES_ROOT = '1abcQuotesRootId';
// Words in a subject line that mark a thread as a quote or proposal.
const QUOTE_HINTS = /\b(quote|quotation|proposal|estimate|sow)\b/i;
// Label applied once a thread's PDFs are filed, so it is not filed twice.
const FILED_LABEL = 'quotes/filed';
/**
* Scans recent mail for quote-like threads, saves any PDF attachments
* into a per-vendor folder, and labels the thread as filed.
*/
function fileQuotes() {
// 1. Recent threads with attachments that have not been filed yet.
const threads = GmailApp.search('has:attachment newer_than:1d -label:quotes/filed');
if (!threads.length) {
Logger.log('No new threads with attachments — nothing to do.');
return;
}
// Find or create the marker label once, outside the loop.
const filed = GmailApp.getUserLabelByName(FILED_LABEL)
|| GmailApp.createLabel(FILED_LABEL);
for (const t of threads) {
// 2. Skip threads whose subject does not look like a quote.
if (!QUOTE_HINTS.test(t.getFirstMessageSubject())) continue;
// 3. Take PDF attachments from the most recent message.
const msg = t.getMessages().slice(-1)[0];
const pdfs = msg.getAttachments()
.filter((a) => a.getContentType() === 'application/pdf');
if (pdfs.length === 0) continue;
// 4. Work out the vendor folder from the sender's address.
const vendor = vendorFromEmail(msg.getFrom());
const folder = getOrCreate(DriveApp.getFolderById(QUOTES_ROOT), vendor);
// 5. Save each PDF into the vendor folder and label the thread.
pdfs.forEach((a) => folder.createFile(a));
t.addLabel(filed);
}
}
/**
* Derives a short vendor name from a "From" header — the first part of
* the sender's domain, e.g. "[email protected]" -> "acme-print".
*/
function vendorFromEmail(from) {
return ((from.match(/@([\w.-]+)/) || [, 'misc'])[1].split('.')[0]) || 'misc';
}
/**
* Returns the named sub-folder inside parent, creating it if missing.
*/
function getOrCreate(parent, name) {
const it = parent.getFoldersByName(name);
return it.hasNext() ? it.next() : parent.createFolder(name);
}
How it works
fileQuotessearches Gmail for threads from the last day that have an attachment and are not already labelledquotes/filed. If none match, it logs a message and stops.- It resolves the
quotes/filedlabel once, creating it on first run. - For each thread it tests the subject of the first message against
QUOTE_HINTS— a regex matching words like “quote”, “proposal” and “sow”. Threads that do not look like quotes are skipped. - It takes the most recent message in the thread (
slice(-1)[0]) and keeps only attachments whose content type isapplication/pdf. A thread with no PDF is skipped. vendorFromEmailderives a short vendor name from the sender’s address by pulling the domain and taking its first label —acme-print.combecomesacme-print, withmiscas the fallback if the address cannot be parsed.getOrCreatefinds the vendor’s sub-folder underquotes/, creating it the first time that vendor sends anything.- Each PDF is saved into that folder with
createFile, and the thread is labelledquotes/filedso the next run leaves it alone.
Example run
A vendor emails Northwind:
From: [email protected] Subject: Quotation for Q3 print run — attached Attachment:
acme-q3-quote.pdf
After a run, Drive looks like this:
quotes/
acme-print/
acme-q3-quote.pdf
The thread is now labelled quotes/filed. A second quote from
[email protected] lands in the same acme-print/ folder, because the
vendor name comes from the domain, not the mailbox. A newsletter with a PDF
attachment but a subject like “May updates” is ignored — it fails the
QUOTE_HINTS test.
Trigger it
Run this on a frequent time-based trigger so quotes file themselves through the day:
- In the Apps Script editor open Triggers (the clock icon).
- Add a trigger for
fileQuotes, Time-driven, Minutes timer, every 30 minutes.
Thirty minutes pairs well with the newer_than:1d search — even if a run is
skipped, the next pass still catches anything from the last 24 hours.
Watch out for
- The subject filter is a blunt instrument. A genuine quote with a vague
subject is missed, and an unrelated mail that happens to say “estimate” is
filed. Tune
QUOTE_HINTSto the language your vendors actually use. - Only PDFs are saved. Vendors who send quotes as
.docxor.xlsxslip through — widen the content-type filter, or pair this with Auto-convert uploaded Office files to Google formats. - Filenames are not de-duplicated. Two quotes named
quote.pdffrom the same vendor produce two files of the same name in one folder. Prefix the name with a date increateFileif that matters. - The vendor name is derived from the domain, so a vendor using a generic
address like
@gmail.comwill be filed undergmailalong with everyone else on that domain. newer_than:1dplus thequotes/filedlabel means a quote only has a 24-hour window to be caught. If the script is paused for a day, older threads will not be filed retroactively — run it manually or widen the search to recover them.
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
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.
Updated Jun 9, 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