File Gmail attachments into storage with names you can find

By General Input

Every hour, new email attachments get sorted by document type and vendor, saved under clear names, and logged in a spreadsheet index.

Integrations

  • Gmail
  • General Input Storage
  • Google Sheets
  • Slack Bot

Type

Agentic Task

Categories

  • Operations
  • Finance

Every hour, sweep my Gmail for new attachments and file them into General Input Storage under names I can actually find later. Run this on an hourly cron schedule.

Finding new mail: use Gmail's List Messages with a search query that combines has:attachment with a time bound covering the window since the last run, for example has:attachment after:<epoch seconds>. Use epoch-second bounds rather than calendar dates, otherwise an hourly sweep re-scans the entire day. Overlap the window by a few minutes so nothing is lost at the boundary; the dedupe step below makes that overlap safe.

Getting the bytes: Gmail exposes no separate attachment operation, so call Get a Message with format set to raw. That returns the whole RFC 2822 message base64url encoded, attachments included. Decode it in the sandbox and walk the MIME parts to pull out each attachment's filename, its real Content-Type, and its bytes. Base64url differs from standard base64, so translate - back to + and _ back to / before decoding.

Skip the junk before classifying anything. Drop parts marked Content-Disposition: inline, anything referenced by a cid: link in the HTML body, images smaller than roughly 20 KB, image parts with no real filename, and the usual signature artifacts such as image001.png. Signature graphics, logos, and tracking pixels must never reach storage; they are the main source of clutter in this pattern.

Classify what survives. Read the sender address and display name, the subject line, and the actual file contents to decide the document type (invoice, receipt, contract, statement, report) and which vendor or client it belongs to. When the subject and the document contents disagree, trust the document. Pull an identifying number when one is present, such as an invoice number or a statement period, since that is what makes the final name useful.

Build a descriptive, store-relative key like invoices/acme-corp/2026-08-invoice-1042.pdf. Lowercase and hyphenate vendor names. Keys use forward slashes only, with no leading slash, no . or .. segments, and a maximum of 1024 characters. Keep the original file extension.

Dedupe in two layers, because one is not enough. First, read the index with Google Sheets Get Values and collect the email message id and attachment filename pairs already recorded, then skip anything present. Second, call Storage's Stat Object on the candidate key before uploading, since it checks existence without downloading. The index check is the load-bearing one: classification can drift slightly between runs and produce a different key for the very same file, which a key-only check would sail straight past and file twice.

Upload with Presign Upload, passing the real sizeBytes because that is what the quota check uses, then PUT the bytes to the returned URL with the returned headers. The Content-Type sent on that PUT is what the object is stored as, so set it from the attachment's actual MIME type rather than a generic fallback. Presigned URLs expire after 15 minutes, so mint one per file and never reuse or store them.

Append a row to the Google Sheets index with Append Values: the date received, the sender, the subject, the document type, the vendor or client, the storage key, and the Gmail message id. The message id column is what keeps re-runs safe, so never omit it.

Anything that cannot be confidently classified goes under an unsorted/ prefix with its original filename preserved, and still gets an index row so it is never invisible. At the end of the sweep, if anything landed in unsorted, post one Slack message with the Slack Bot's Send a Message listing each file, its sender, and the reason it could not be placed, so a person can move it. Send nothing when everything classified cleanly; no empty all-clear pings.

Never trash, relabel, or otherwise modify the source email. This workflow only reads mail.

Related prompts

Explore more prompts
Call overdue Xero customers with an AI collections agentLocal listing health board for every location you manageWin back LiveChat visitors whose chats went unansweredLet support send one-off Loops emails without an engineerA brand asset library your marketing team actually searchesTurn Mailjet email clicks into ranked HubSpot follow-upsClean out the Looker dashboards and Looks nobody opensStop cold emails to anyone with a live deal in PipedriveLiveKit live operations console for room moderationWake up dormant Keap leads with a researched reason