Files every document that arrives by email into Box and records what it says as a row on your register.

UsesBoxGmailGoogle SheetsOpenAI

File emailed documents and log what they say

Whenever new mail arrives in Gmail, the workflow sweeps your inbox for messages matching a search you describe, copies the PDF attachments on each into a Box folder and adds one row per document to a register in Google Sheets. The row records when it was filed, the sender, the subject, the name it was filed under, a link to the copy and whether it could be read, followed by details read from the document itself, such as a supplier, document number, date and total, written exactly as the document gives them.

A field the document does not carry is left empty rather than guessed. Each copy keeps its own name plus a reference to its email, so this month's Invoice.pdf never collides with last month's. An email is labelled as registered only once every document on it is copied and logged. One that cannot be finished, such as an older email from a sender whose newer one is still in the inbox, is labelled 'Needs filing' for you, and any problem, including a document that could not be read, ends the run marked as failed with a note of what to do.

You need Gmail, Box, Google Sheets and OpenAI connected, a Box folder to file into, and a spreadsheet tab whose column headings match the row. Fill in the Gmail search, the Box folder and the spreadsheet link; everything else has a working default.

GmailGmailWhen an email arrives.When an email arrives.
ValuesSet documentSearch to blank.Set documentSearch to blank.
ValuesSet boxFolder to blank.Set boxFolder to blank.
ValuesSet registerSheetUrl to blank.Set registerSheetUrl to blank.
ValuesSet sheetTabName to Documents.Set sheetTabName to Documents.
ValuesSet documentFields to Supplier, Document number, Document date, Total amount, Currency.Set documentFields to Supplier, Document number, Document date, Total amount, Currency.
ValuesSet processedLabel to Document register.Set processedLabel to Document register.
ValuesSet needsFilingLabel to Needs filing.Set needsFilingLabel to Needs filing.
ValuesSet maxDocumentsPerRun to 5.Set maxDocumentsPerRun to 5.
MathSet documentCap to maxDocumentsPerRun multiplied by 1.Set documentCap to maxDocumentsPerRun multiplied by 1.
Decisions & LoopsIf documentCap is greater than 25If documentCap is greater than 25
Yes
Decisions & LoopsStop and report a problem: The 'maxDocumentsPerRun' step needs a whole number between 1 and 25 - it is how many documents one run is allowed to handle.Stop and report a problem: The 'maxDocumentsPerRun' step needs a whole number between 1 and 25 - it is how many documents one run is allowed to handle.
No
No steps
Decisions & LoopsIf documentCap is less than 1If documentCap is less than 1
Yes
Decisions & LoopsStop and report a problem: The 'maxDocumentsPerRun' step needs a whole number between 1 and 25 - it is how many documents one run is allowed to handle.Stop and report a problem: The 'maxDocumentsPerRun' step needs a whole number between 1 and 25 - it is how many documents one run is allowed to handle.
No
No steps
TextTrim boxFolder and save it as boxFolderId.Trim boxFolder and save it as boxFolderId.
Decisions & LoopsIf boxFolderId contains /folder/ (ignoring case)If boxFolderId contains /folder/ (ignoring case)
Yes
TextTrim the first item of the last item of boxFolderId split by /folder/ split by / and save it as boxFolderId.Trim the first item of the last item of boxFolderId split by /folder/ split by / and save it as boxFolderId.
No
No steps
Decisions & LoopsIf boxFolderId is empty or boxFolderId multiplied by 1 is not boxFolderIdIf boxFolderId is empty or boxFolderId multiplied by 1 is not boxFolderId
Yes
Decisions & LoopsStop and report a problem: The 'boxFolder' step needs a Box folder id, which is a number - open the folder in Box and copy either its whole address or just the number after '/folder/'.Stop and report a problem: The 'boxFolder' step needs a Box folder id, which is a number - open the folder in Box and copy either its whole address or just the number after '/folder/'.
No
No steps
ValuesSet fieldNames to an empty list.Set fieldNames to an empty list.
Decisions & LoopsFor each rawFieldName in documentFields split by ,:For each rawFieldName in documentFields split by ,:
TextTrim rawFieldName and save it as fieldName.Trim rawFieldName and save it as fieldName.
Decisions & LoopsIf fieldName isn't emptyIf fieldName isn't empty
Yes
ListsAdd fieldName to the end of fieldNames and save it as fieldNames.Add fieldName to the end of fieldNames and save it as fieldNames.
No
No steps
Decisions & LoopsIf fieldNames is emptyIf fieldNames is empty
Yes
Decisions & LoopsStop and report a problem: The 'documentFields' step needs at least one field name, separated by commas - those are the columns the register records.Stop and report a problem: The 'documentFields' step needs at least one field name, separated by commas - those are the columns the register records.
No
No steps
GmailGmailList emails matching '@documentSearch has:attachment -label:@processedLabel -label:@needsFilingLabel'. Save it as found.List emails matching '@documentSearch has:attachment -label:@processedLabel -label:@needsFilingLabel'. Save it as found.
Dates & TimesGet the current date and time and save it as runStarted.Get the current date and time and save it as runStarted.
Dates & TimesMake runStarted readable as “2026-11-02 14:05” and save it as filedAt.Make runStarted readable as “2026-11-02 14:05” and save it as filedAt.
ValuesSet filedCount to 0.Set filedCount to 0.
ValuesSet messagesLeft to 0.Set messagesLeft to 0.
ValuesSet setAside to an empty list.Set setAside to an empty list.
ValuesSet unreadDocuments to an empty list.Set unreadDocuments to an empty list.
ValuesSet readProblem to blank.Set readProblem to blank.
Decisions & LoopsFor each message in found.emails:For each message in found.emails:
Decisions & LoopsIf filedCount is greater than or equal to documentCapIf filedCount is greater than or equal to documentCap
Yes
ValuesAdd 1 to messagesLeft.Add 1 to messagesLeft.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet messageDescription to an email.Set messageDescription to an email.
Decisions & LoopsIf a step below fails, save the error as messageErrorIf a step below fails, save the error as messageError
Try
ValuesSet messageId to blank.Set messageId to blank.
Decisions & LoopsIf a step below fails, save the error as idErrorIf a step below fails, save the error as idError
Try
TextTrim message.id and save it as messageId.Trim message.id and save it as messageId.
On error
No steps
Decisions & LoopsIf messageId is emptyIf messageId is empty
Yes
ValuesShow in the output: Skipped a message Gmail returned without an id..Show in the output: Skipped a message Gmail returned without an id..
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet rawFrom to blank.Set rawFrom to blank.
Decisions & LoopsIf a step below fails, save the error as fromErrorIf a step below fails, save the error as fromError
Try
TextTrim message.from and save it as rawFrom.Trim message.from and save it as rawFrom.
On error
No steps
ValuesSet senderAddress to rawFrom.Set senderAddress to rawFrom.
Decisions & LoopsIf rawFrom contains < (ignoring case)If rawFrom contains < (ignoring case)
Yes
TextTrim the first item of the last item of rawFrom split by < split by > and save it as senderAddress.Trim the first item of the last item of rawFrom split by < split by > and save it as senderAddress.
No
No steps
ValuesSet subject to (no subject).Set subject to (no subject).
Decisions & LoopsIf a step below fails, save the error as subjectErrorIf a step below fails, save the error as subjectError
Try
TextTrim message.subject and save it as subjectText.Trim message.subject and save it as subjectText.
Decisions & LoopsIf subjectText isn't emptyIf subjectText isn't empty
Yes
ValuesChange subject to subjectText.Change subject to subjectText.
No
No steps
On error
No steps
TextBuild the text '@subject' from @senderAddress and save it as messageDescription.Build the text '@subject' from @senderAddress and save it as messageDescription.
ValuesSet messageProblems to an empty list.Set messageProblems to an empty list.
Decisions & LoopsIf senderAddress doesn't contain @ (ignoring case)If senderAddress doesn't contain @ (ignoring case)
Yes
ListsAdd its sender could not be read (rawFrom) to the end of messageProblems and save it as messageProblems.Add its sender could not be read (rawFrom) to the end of messageProblems and save it as messageProblems.
No
ValuesSet latestId to blank.Set latestId to blank.
GmailGmailFind email matching 'from:senderAddress'. Save it as latestFromSender.Find email matching 'from:senderAddress'. Save it as latestFromSender.
Decisions & LoopsIf a step below fails, save the error as latestErrorIf a step below fails, save the error as latestError
Try
TextTrim latestFromSender.email.id and save it as latestId.Trim latestFromSender.email.id and save it as latestId.
On error
No steps
Decisions & LoopsIf latestId is not messageIdIf latestId is not messageId
Yes
ListsAdd only a sender's newest email can be opened, and a newer one from this sender is in the inbox; archive that one first to the end of messageProblems and save it as messageProblems.Add only a sender's newest email can be opened, and a newer one from this sender is in the inbox; archive that one first to the end of messageProblems and save it as messageProblems.
No
GmailGmailDownload the attachments on the latest email from senderAddress. Save it as downloaded.Download the attachments on the latest email from senderAddress. Save it as downloaded.
ValuesSet messageDocuments to 0.Set messageDocuments to 0.
ValuesSet attachmentNumber to 0.Set attachmentNumber to 0.
ValuesSet namesOnMessage to an empty list.Set namesOnMessage to an empty list.
Decisions & LoopsFor each attachment in downloaded.documents:For each attachment in downloaded.documents:
Values Set fileName to blank. Set fileName to blank.
Decisions & Loops If a step below fails, save the error as nameError If a step below fails, save the error as nameError
Try
TextTrim attachment.filename and save it as fileName.Trim attachment.filename and save it as fileName.
On error
No steps
Values Set attachmentLabel to (unnamed attachment). Set attachmentLabel to (unnamed attachment).
Decisions & Loops If fileName isn't empty If fileName isn't empty
Yes
ValuesChange attachmentLabel to fileName.Change attachmentLabel to fileName.
No
No steps
Decisions & Loops If fileName in lowercase doesn't end with .pdf (ignoring case) If fileName in lowercase doesn't end with .pdf (ignoring case)
Yes
ValuesShow in the output: Skipped attachmentLabel: only PDF attachments are treated as documents.Show in the output: Skipped attachmentLabel: only PDF attachments are treated as documents.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Values Set documentRef to blank. Set documentRef to blank.
Decisions & Loops If a step below fails, save the error as refError If a step below fails, save the error as refError
Try
TextTrim attachment.document_ref and save it as documentRef.Trim attachment.document_ref and save it as documentRef.
On error
No steps
Decisions & Loops If documentRef is empty If documentRef is empty
Yes
ValuesShow in the output: Could not open attachmentLabel; it was left alone.Show in the output: Could not open attachmentLabel; it was left alone.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Values Add 1 to attachmentNumber. Add 1 to attachmentNumber.
Text Replace the pattern \.pdf$ with in attachmentLabel (ignoring case) and save it as fileStem. Replace the pattern \.pdf$ with in attachmentLabel (ignoring case) and save it as fileStem.
Text Build the text @fileStem (@messageId).pdf and save it as boxName. Build the text @fileStem (@messageId).pdf and save it as boxName.
Decisions & Loops If namesOnMessage contains boxName If namesOnMessage contains boxName
Yes
TextBuild the text @fileStem (@messageId @attachmentNumber).pdf and save it as boxName.Build the text @fileStem (@messageId @attachmentNumber).pdf and save it as boxName.
No
No steps
Lists Add boxName to the end of namesOnMessage and save it as namesOnMessage. Add boxName to the end of namesOnMessage and save it as namesOnMessage.
Values Set storedLink to blank. Set storedLink to blank.
Values Set uploadProblem to blank. Set uploadProblem to blank.
Decisions & Loops If a step below fails, save the error as uploadError If a step below fails, save the error as uploadError
Try
BoxBoxUpload boxFolderId to Box folder documentRef. Save it as stored.Upload boxFolderId to Box folder documentRef. Save it as stored.
TextTrim stored.file_link and save it as storedLink.Trim stored.file_link and save it as storedLink.
On error
TextSplit uploadError.message by a new line and save it as uploadErrorLines.Split uploadError.message by a new line and save it as uploadErrorLines.
ListsGet the first item of uploadErrorLines and save it as uploadProblem.Get the first item of uploadErrorLines and save it as uploadProblem.
Decisions & LoopsIf a step below fails, save the error as lookupErrorIf a step below fails, save the error as lookupError
Try
BoxBoxList items in Box folder boxFolderId. Save it as folderItems.List items in Box folder boxFolderId. Save it as folderItems.
ListsKeep each item in folderItems.items where item.name is boxName and save it as sameName.Keep each item in folderItems.items where item.name is boxName and save it as sameName.
Decisions & LoopsIf sameName isn't emptyIf sameName isn't empty
Yes
TextBuild the text https://app.box.com/file/@sameName[0].id and save it as storedLink.Build the text https://app.box.com/file/@sameName[0].id and save it as storedLink.
No
No steps
On error
No steps
Decisions & Loops If storedLink is empty If storedLink is empty
Yes
Decisions & LoopsIf filedCount is 0If filedCount is 0
Yes
Decisions & LoopsStop and report a problem: Nothing was filed this run: attachmentLabel on messageDescription could not be copied into Box (uploadProblem). Check that Box is connected and that the 'boxFolder' step names a folder you can add files to. The email is left as it was, so the next run tries it again.Stop and report a problem: Nothing was filed this run: attachmentLabel on messageDescription could not be copied into Box (uploadProblem). Check that Box is connected and that the 'boxFolder' step names a folder you can add files to. The email is left as it was, so the next run tries it again.
No
No steps
ListsAdd attachmentLabel could not be copied into Box (uploadProblem) to the end of messageProblems and save it as messageProblems.Add attachmentLabel could not be copied into Box (uploadProblem) to the end of messageProblems and save it as messageProblems.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Values Set readStatus to Read. Set readStatus to Read.
Values Set returnedNames to an empty list. Set returnedNames to an empty list.
Values Set returnedValues to an empty list. Set returnedValues to an empty list.
Decisions & Loops If a step below fails, save the error as extractError If a step below fails, save the error as extractError
Try
AI AgentRun an AI agent: Read the attached document and pull out each of these fields, in this order: - fieldName… and save it as extraction.Run an AI agent: Read the attached document and pull out each of these fields, in this order: - fieldName… and save it as extraction.
Decisions & LoopsFor each returnedField in extraction.output.fields:For each returnedField in extraction.output.fields:
Values Set returnedName to blank. Set returnedName to blank.
Decisions & Loops If a step below fails, save the error as fieldNameError If a step below fails, save the error as fieldNameError
Try
TextTrim returnedField.name and save it as returnedName.Trim returnedField.name and save it as returnedName.
On error
No steps
Values Set returnedValue to blank. Set returnedValue to blank.
Decisions & Loops If a step below fails, save the error as fieldValueError If a step below fails, save the error as fieldValueError
Try
TextTrim returnedField.value and save it as returnedValue.Trim returnedField.value and save it as returnedValue.
On error
Decisions & LoopsIf a step below fails, save the error as fieldListErrorIf a step below fails, save the error as fieldListError
Try
TextJoin returnedField.value with , and save it as returnedValue.Join returnedField.value with , and save it as returnedValue.
On error
No steps
Lists Add returnedName to the end of returnedNames and save it as returnedNames. Add returnedName to the end of returnedNames and save it as returnedNames.
Lists Add returnedValue to the end of returnedValues and save it as returnedValues. Add returnedValue to the end of returnedValues and save it as returnedValues.
On error
ValuesChange readStatus to Filed but could not be read.Change readStatus to Filed but could not be read.
ListsAdd boxName to the end of unreadDocuments and save it as unreadDocuments.Add boxName to the end of unreadDocuments and save it as unreadDocuments.
ValuesChange readProblem to extractError.message.Change readProblem to extractError.message.
Values Set rowValues to an empty list. Set rowValues to an empty list.
Lists Add filedAt to the end of rowValues and save it as rowValues. Add filedAt to the end of rowValues and save it as rowValues.
Lists Add senderAddress to the end of rowValues and save it as rowValues. Add senderAddress to the end of rowValues and save it as rowValues.
Lists Add subject to the end of rowValues and save it as rowValues. Add subject to the end of rowValues and save it as rowValues.
Lists Add boxName to the end of rowValues and save it as rowValues. Add boxName to the end of rowValues and save it as rowValues.
Lists Add storedLink to the end of rowValues and save it as rowValues. Add storedLink to the end of rowValues and save it as rowValues.
Lists Add readStatus to the end of rowValues and save it as rowValues. Add readStatus to the end of rowValues and save it as rowValues.
Decisions & Loops For each wantedField in fieldNames: For each wantedField in fieldNames:
Values Set cellValue to blank. Set cellValue to blank.
Lists Find the position of wantedField in returnedNames and save it as foundAt. Find the position of wantedField in returnedNames and save it as foundAt.
Decisions & Loops If foundAt is greater than or equal to 0 If foundAt is greater than or equal to 0
Yes
ListsChange cellValue to returnedValues[@foundAt].Change cellValue to returnedValues[@foundAt].
No
No steps
Lists Add cellValue to the end of rowValues and save it as rowValues. Add cellValue to the end of rowValues and save it as rowValues.
Decisions & Loops If a step below fails, save the error as registerError If a step below fails, save the error as registerError
Try
Google SheetsGoogle SheetsAdd a row to sheetTabName in registerSheetUrl.Add a row to sheetTabName in registerSheetUrl.
On error
Decisions & LoopsIf filedCount is 0If filedCount is 0
Yes
Decisions & LoopsStop and report a problem: Nothing was added to the register this run: the row for boxName could not be written (registerError.message). Check that Google Sheets is connected and that 'registerSheetUrl' and 'sheetTabName' point at your register. The copy is already in Box; the email is left as it was, so the next run tries again and uses that copy.Stop and report a problem: Nothing was added to the register this run: the row for boxName could not be written (registerError.message). Check that Google Sheets is connected and that 'registerSheetUrl' and 'sheetTabName' point at your register. The copy is already in Box; the email is left as it was, so the next run tries again and uses that copy.
No
No steps
ListsAdd boxName is in Box but could not be added to the register (registerError.message) to the end of messageProblems and save it as messageProblems.Add boxName is in Box but could not be added to the register (registerError.message) to the end of messageProblems and save it as messageProblems.
Decisions & LoopsSkip to the next item.Skip to the next item.
Values Add 1 to filedCount. Add 1 to filedCount.
Values Add 1 to messageDocuments. Add 1 to messageDocuments.
Values Show in the output: Filed boxName and added it to the register. Show in the output: Filed boxName and added it to the register.
Decisions & LoopsIf messageProblems is empty and messageDocuments is 0If messageProblems is empty and messageDocuments is 0
Yes
ValuesShow in the output: Nothing to register on messageDescription - it carried no PDF.Show in the output: Nothing to register on messageDescription - it carried no PDF.
No
No steps
Decisions & LoopsIf messageProblems is emptyIf messageProblems is empty
Yes
GmailGmailAdd label processedLabel to email messageId.Add label processedLabel to email messageId.
No
TextJoin messageProblems with ; and save it as problemText.Join messageProblems with ; and save it as problemText.
ListsAdd messageDescription: problemText to the end of setAside and save it as setAside.Add messageDescription: problemText to the end of setAside and save it as setAside.
GmailGmailAdd label needsFilingLabel to email messageId.Add label needsFilingLabel to email messageId.
On error
Decisions & LoopsIf filedCount is 0If filedCount is 0
Yes
Decisions & LoopsStop and report a problem: Nothing was filed this run: messageDescription could not be handled (messageError.message). The email is left as it was, so the next run tries it again.Stop and report a problem: Nothing was filed this run: messageDescription could not be handled (messageError.message). The email is left as it was, so the next run tries it again.
No
No steps
ListsAdd messageDescription: messageError.message to the end of setAside and save it as setAside.Add messageDescription: messageError.message to the end of setAside and save it as setAside.
Decisions & LoopsIf a step below fails, save the error as setAsideErrorIf a step below fails, save the error as setAsideError
Try
GmailGmailAdd label needsFilingLabel to email message.Add label needsFilingLabel to email message.
On error
No steps
Decisions & LoopsIf filedCount is 0 and setAside is empty and messagesLeft is 0If filedCount is 0 and setAside is empty and messagesLeft is 0
Yes
Decisions & LoopsFinish the run: No new documents to add to the register.Finish the run: No new documents to add to the register.
No
No steps
ValuesSet reportLines to an empty list.Set reportLines to an empty list.
ListsAdd Filed filedCount document(s) into Box and onto the register. to the end of reportLines and save it as reportLines.Add Filed filedCount document(s) into Box and onto the register. to the end of reportLines and save it as reportLines.
Decisions & LoopsIf messagesLeft is greater than 0If messagesLeft is greater than 0
Yes
ListsAdd messagesLeft more message(s) are waiting: this run reached its limit of maxDocumentsPerRun documents, and the next run carries on from there. to the end of reportLines and save it as reportLines.Add messagesLeft more message(s) are waiting: this run reached its limit of maxDocumentsPerRun documents, and the next run carries on from there. to the end of reportLines and save it as reportLines.
No
No steps
Decisions & LoopsIf unreadDocuments isn't emptyIf unreadDocuments isn't empty
Yes
TextJoin unreadDocuments with , and save it as unreadList.Join unreadDocuments with , and save it as unreadList.
ListsAdd Filed, but the details could not be read, so their rows are empty for you to fill in: unreadList. The reader said: readProblem to the end of reportLines and save it as reportLines.Add Filed, but the details could not be read, so their rows are empty for you to fill in: unreadList. The reader said: readProblem to the end of reportLines and save it as reportLines.
No
No steps
Decisions & LoopsIf setAside isn't emptyIf setAside isn't empty
Yes
TextJoin setAside with "Join setAside with - and save it as setAsideList.
ListsAdd These emails could not be finished and are labelled '@needsFilingLabel'. Fix the cause and remove the label to have one tried again, or file it by hand:Add These emails could not be finished and are labelled '@needsFilingLabel'. Fix the cause and remove the label to have one tried again, or file it by hand: - setAsideList to the end of reportLines and save it as reportLines.
No
No steps
TextJoin reportLines with a new line and save it as runReport.Join reportLines with a new line and save it as runReport.
Decisions & LoopsIf setAside isn't emptyIf setAside isn't empty
Yes
Decisions & LoopsStop and report a problem: runReport.Stop and report a problem: runReport.
No
No steps
Decisions & LoopsIf unreadDocuments isn't emptyIf unreadDocuments isn't empty
Yes
Decisions & LoopsStop and report a problem: runReport.Stop and report a problem: runReport.
No
No steps
Decisions & LoopsFinish the run: runReport.Finish the run: runReport.