An email attachment usually needs two more homes: a folder where it can be found and a record explaining where it came from. This walkthrough designs a Make scenario that saves Gmail attachments to Google Drive and records them in Google Sheets. A stable identifier and a processing record help prevent a completed source item from being uploaded again.
This is a documentation-based walkthrough with fictional practice data. It has not been executed in a Make account. Module names were checked on October 4, 2026; verify the mappings and results in an authorized test account before using real data.
Prepare a small private test
Use a Google test account, a Gmail label named MAKE_TEST, a private Drive folder named Make attachment practice and a Sheet named attachment_log. Use small fictional text files with neutral names. Do not make the folder public.
Create these column headings in the first row without merged cells:
dedup_key, message_id, attachment_id, original_name, drive_file_id, status, processed_at
Keep identifiers as text. The workflow does not need the email body, phone numbers or recipient list. File names can contain personal information, so avoid real customer documents while learning.
Confirm your Make account has the required modules and storage. Free has a documented 5 MB per-file limit; the practice files should be much smaller. Google storage capacity and service quotas remain separate constraints.
Step 1 Review connections and keep scheduling off
Create a scenario and add Gmail V4. Review the selected Google account and the permission screen. The task needs access to source messages and attachments, permission to create files in the destination and permission to write to the Sheet. The offered connection may request broader access. Stop if that is inappropriate and consult the account administrator.
A label limits scenario processing; it does not necessarily limit the OAuth grant. Do not add API scopes, service accounts or custom credentials just to follow this example. Keep scheduling off and use a small manual run when the connection and test are authorized.
Step 2 Limit the source messages
In Watch emails, select MAKE_TEST, restrict the source to emails with attachments and set a limit of one message per cycle for the first trial. Choose not to mark messages as read. Select a starting point that covers only the test messages, rather than importing the account history.
Unread status is not a duplicate-prevention mechanism. Someone can open a message, and a retry can revisit data that already reached a destination. This design identifies the actual message and attachment instead.
Step 3 Retrieve each attachment and its data
Add List email attachments and media. Map Message ID from the trigger, choose attachments and enable returning file data. The exercise does not need inline signature images.
Inspect a sample result before continuing. You need the original name and Data for the upload. If the module already returns a separate bundle for each attachment, do not add another Iterator. Add an Iterator only if your account returns one bundle containing an attachment array, then recheck the number of downstream items.
Identify a stable attachment identifier exposed in that result, such as Attachment ID or Part ID. Do not use the operation number or an item's position in a list. If your module output does not expose a stable identifier, stop before any destination writes and verify how to obtain it for that version. A connector change needs another permission and mapping review. File name alone is not a safe substitute.
Before key creation, add a Filter requiring a nonempty file name, Message ID and stable attachment identifier. Also check that Data is available for upload. A valid zero-byte file is different from a missing Data field; do not reject it just for having zero length. Record blocked items for manual review; a filter does not create an error record by itself.
Step 4 Build a repeatable key
Use one Tools module to set the source-key string and a second Tools module after it to calculate the hash. Join a fixed account alias, Message ID and the attachment identifier with an unchanged separator. The example alias practice01 is not an email address. Assign it to one source account and use one writer for this key namespace. Verify that the separator cannot appear inside any component; otherwise use a tested unambiguous serialization. Hashing does not repair an ambiguous joined string.
Illustrative structure: practice01|msg_demo_01|att_demo_A
Apply sha256 to that text with the default hex encoding and store the result as dedup_key. In the actual mapping, select real module fields rather than typing these fictional identifiers. Keep the alias and separator stable for the life of the scenario.
Two different emails containing report.txt should have different keys. Reprocessing the same attachment in the same email should produce the same key. Two attachments in one email need different attachment identifiers. The design prevents repeated processing of the same source item; it does not identify every identical file sent in a different message.
Step 5 Reserve the item before uploading
Before creating the store, check the account’s unused allocation. According to Make’s documentation, Free includes 1 MB of Data store storage, and each store requires at least 1 MB. That allocation supports one store, not several separate stores for these exercises at the same time. If it is already allocated, stop before creating another store; do not delete existing data or activate a paid upgrade just to follow the exercise.
Create a practice Data Store with text fields status, message_id, attachment_id, original_name, drive_file_id and updated_at. Use dedup_key as the record key. Enable Process data in order for this scenario to avoid overlapping executions within it.
Add Check the Existence of a Record for the key, followed by a Router with two routes. On the no-record route, use Add/Replace a Record with that key, status=reserved and Overwrite an existing record disabled. Also map message_id, attachment_id, original_name and updated_at, leaving drive_file_id empty at this point. Continue to Drive only after the reservation succeeds.
On the existing-record route, use Get a Record. A complete record ends processing for that item without an upload or new Sheet row. Any other state is held for manual reconciliation. A duplicate-key error while creating the reservation must also stop before the upload.
This design uses Commit after each module so that the reservation can remain after a later failure in a transactional module. It deliberately preserves intermediate progress. Do not then expect an automatic whole-workflow rollback. Verify that a reserved record survives an intentional downstream failure before enabling scheduling.
Step 6 Upload and record the result
On the new-item route only, add Google Drive — Upload a File and explicitly select the practice folder. Map the attachment Data. Build the destination file name from dedup_key, two underscores and the original name. Keep file conversion off to preserve the file content.
After a successful upload, update the Data Store record with the returned drive_file_id and status=uploaded. Add Google Sheets — Add a Row, select the practice spreadsheet and map the values into their matching columns. Set the row status to complete and write an ISO timestamp with a timezone. Use Raw input where that option is offered so text is not interpreted as a formula.
In every Update a Record, map the same dedup_key and leave Insert missing record disabled. After the row is confirmed, update the Data Store record to complete. The scenario does not need a sharing module or an outbound email. For one successful key, the end state is one Drive file, one tracking row and one state record.
Step 7 Check the cases that cause duplicates
These are expected design outcomes, not observed Make results:
- One message with one attachment produces one file and one tracking row.
- Reprocessing that source attachment adds neither a file nor a row.
- One message with two attachments produces two distinct records and rows.
- A different message with the same file name creates a new item because the source is different.
- A message with no attachment is excluded before file processing.
- A missing name or stable attachment identifier stops before destination writes.
- A Sheets failure after upload leaves the file and uploaded state for reconciliation, without a fresh upload.
- An upload succeeds but the uploaded-state update fails: inspect the real file and reconcile the register.
- A Sheet append succeeds but the complete update fails: find the key in the Sheet and update only the state.
- The first attachment fails and a second remains: account for the attachment that never reached its processing route.
Compare the identifiers in Drive, Sheets and the Data Store. Also test the uncertain-outcome case: a destination may complete a write even if Make does not receive its confirmation. An existing key helps stop another attempt; it does not prove what happened at the destination.
Recover only the missing part
For a reserved item, inspect history and the destination folder to determine whether the upload happened before its status update failed. For uploaded, first search the Sheet for dedup_key. Complete only the missing write and then reconcile the state record.
Do not delete the reservation merely to make a rerun possible. For timeouts, permissions or quota errors, hold the item and fix the cause. Avoid automatically retrying an upload or append when you cannot establish whether it already succeeded.
Keep Store incomplete executions off for this trial. Attach a Commit error route to the upload, Sheet append and subsequent state updates. The aim is to preserve supported transactional progress and stop for reconciliation without adding a Retry route to writes. Confirm during the failure test that no automatic retry remains pending. Stopping a run does not necessarily disable its schedule.
Execution logs can reveal inputs and outputs. Before handling real information, decide who can inspect them and what should be retained. Confidentiality settings reduce troubleshooting detail. Keep the test source available so unprocessed items can be recovered after a run stops.
Commit stops the whole run, including remaining bundles. Start the failure trial with one email and one attachment, then test multiple attachments. Keep the fetched message and attachment identifiers in the incident record and compare them with destination items. Ordinary Run once does not guarantee that the polling trigger will retrieve them again because its position may have advanced.
If the historical trigger input is still available, use Replay run for that execution only after reconciling items already written. Replay uses the current scenario and revisits steps that previously succeeded. Preserve the keys, check that no other attempt is active and account for each unfinished item. If history cannot be replayed, recover only the identified source messages through a limited authorized recovery process. Do not reset the entire mailbox history.
Decide when to schedule it
Only consider scheduling after the acceptance cases pass, an owner is responsible for exceptions and actual credit use has been recorded. Choose a frequency that suits the business need. Keep a short record of the scenario version, mappings and recovery procedure.
The desired result is a useful folder and a tracking Sheet you can explain. Duplicate prevention depends on stable source identity, persistent progress and careful handling of partial success. A first successful run is only one part of that check.
Download the practice files
This pack contains original, manually authored examples, fictional data and expected test outcomes. It has not been executed in Make, is not an importable blueprint and does not run an automation. Use it with the guide in an authorized test environment only.
Download the Make practice pack (ZIP, 6 KB)
For workflow fit, costs and maintenance, read our Make review for small businesses.
Sources
- Make Help Center: Data stores


