Reads order emails hourly, has AI pull out each line item, and appends it to your order sheet.
Uses
Gmail
Google Sheets
OpenAI
Turn order emails into spreadsheet rows
Every hour, the order emails matching your Gmail search are read from your inbox, an AI pulls out who is ordering and every item and quantity, and one row per item is added to the tab you name in your spreadsheet. Each row records the customer, order reference, item, quantity, requested delivery date and the email it came from, so any row can be traced back. An order that gives no reference or delivery date is still logged, with 'Not given' in that column.
The workflow refuses to guess. If an email is not clearly an order, or any item's name or quantity is missing or unclear, nothing from it is written and it is labelled 'Order needs review' for a person. Handled emails are labelled 'Order logged' and both labels are left out of the search, so no order is recorded twice. If the AI cannot be reached or the sheet will not take rows, the emails are left unlabelled to be tried again next hour and the run is marked as failed, saying what went wrong.
You need Gmail, Google Sheets and OpenAI connected, a link to your spreadsheet, the tab's name with the six headings in row 1, and a Gmail search that finds your order emails.
spreadsheetUrl to blank.Set spreadsheetUrl to blank.sheetName to blank.Set sheetName to blank.orderEmailSearch to blank.Set orderEmailSearch to blank.maxEmails to 25.Set maxEmails to 25.loggedLabel to Order logged.Set loggedLabel to Order logged.reviewLabel to Order needs review.Set reviewLabel to Order needs review.tabHeadings to an empty list.Set tabHeadings to an empty list.tabErrorIf a step below fails, save the error as tabErrorspreadsheetUrl. Save it as orderTab.Read the rows of spreadsheetUrl. Save it as orderTab.tabHeadings to orderTab.headers.Change tabHeadings to orderTab.headers.tabError.message). Check the link in 'spreadsheetUrl' and the tab name in 'sheetName', then run it again.Stop and report a problem: Could not open the '@sheetName' tab of your order spreadsheet (tabError.message). Check the link in 'spreadsheetUrl' and the tab name in 'sheetName', then run it again.tabHeadings is emptyIf tabHeadings is emptytabHeadings and save it as firstHeadings.Take the first 6 items of tabHeadings and save it as firstHeadings.firstHeadings with , and save it as headingLine.Join firstHeadings with , and save it as headingLine.headingLine in lowercase is not "customer, order reference, item, quantity,…If headingLine in lowercase is not "customer, order reference, item, quantity,…headingLine. Fix the headings, then run it again.Stop and report a problem: Rows are added to the '@sheetName' tab column by column, so row 1 must read: Customer, Order reference, Item, Quantity, Requested delivery date, Gmail message ID. It reads: headingLine. Fix the headings, then run it again.@loggedLabel -label:@reviewLabel'. Save it as inbox.List emails matching '@orderEmailSearch -label:@loggedLabel -label:@reviewLabel'. Save it as inbox.rowsAdded to 0.Set rowsAdded to 0.ordersLogged to 0.Set ordersLogged to 0.reviewNotes to an empty list.Set reviewNotes to an empty list.failures to an empty list.Set failures to an empty list.aiFailed to an empty list.Set aiFailed to an empty list.lastAiProblem to blank.Set lastAiProblem to blank.email in inbox.emails:For each email in inbox.emails:email.subject, or (no subject) if it is empty and save it as subject.Use email.subject, or (no subject) if it is empty and save it as subject.email.body and save it as emailBody.Trim email.body and save it as emailBody.reviewReason to it has no text to read; an order sent only as an attachment has to be typed in by hand.Set reviewReason to it has no text to read; an order sent only as an attachment has to be typed in by hand.orderRows to an empty list.Set orderRows to an empty list.emailBody isn't emptyIf emailBody isn't emptyextracted to an empty record.Set extracted to an empty record.aiProblem to blank.Set aiProblem to blank.aiErrorIf a step below fails, save the error as aiErroremail.from Subject: subject emailBody and save it as reading.Run an AI agent: Copy the order out of this email. From: email.from Subject: subject emailBody and save it as reading.extracted to reading.output.Change extracted to reading.output.aiProblem to aiError.message.Change aiProblem to aiError.message.aiProblem isn't emptyIf aiProblem isn't emptyaiFailed and save it as aiFailed.Add '@subject' to the end of aiFailed and save it as aiFailed.lastAiProblem to aiProblem.Change lastAiProblem to aiProblem.extracted.customer and save it as customer.Trim extracted.customer and save it as customer.extracted.order_reference, or Not given if it is empty and save it as orderReference.Use extracted.order_reference, or Not given if it is empty and save it as orderReference.extracted.delivery_date, or Not given if it is empty and save it as deliveryDate.Use extracted.delivery_date, or Not given if it is empty and save it as deliveryDate.incompleteLines to 0.Set incompleteLines to 0.line in extracted.items:For each line in extracted.items:line.item and save it as itemName. Trim line.item and save it as itemName.itemName isn't empty and line.quantity is greater than 0 If itemName isn't empty and line.quantity is greater than 0customer, orderReference, itemName, line.quantity, deliveryDate, email.id] to the end of orderRows and save it as orderRows.Add [customer, orderReference, itemName, line.quantity, deliveryDate, email.id] to the end of orderRows and save it as orderRows.incompleteLines.Add 1 to incompleteLines.extracted.is_order is noIf extracted.is_order is noreviewReason to the AI did not see an order in it.Change reviewReason to the AI did not see an order in it.reviewReason to the AI could not tell whether it places an order.Change reviewReason to the AI could not tell whether it places an order.reviewReason to it does not say who the customer is.Change reviewReason to it does not say who the customer is.reviewReason to no ordered item was found in it.Change reviewReason to no ordered item was found in it.reviewReason to an item's name or quantity is missing or unclear.Change reviewReason to an item's name or quantity is missing or unclear.reviewReason to blank.Change reviewReason to blank.reviewReason isn't emptyIf reviewReason isn't emptyreviewLabelErrorIf a step below fails, save the error as reviewLabelErrorreviewLabel to email email. Save it as flagged.Add label reviewLabel to email email. Save it as flagged.reviewReason) to the end of reviewNotes and save it as reviewNotes.Add '@subject' (reviewReason) to the end of reviewNotes and save it as reviewNotes.reviewReason) but the label could not be added (reviewLabelError.message) to the end of failures and save it as failures.Add '@subject' needs review (reviewReason) but the label could not be added (reviewLabelError.message) to the end of failures and save it as failures.rowsWritten to 0.Set rowsWritten to 0.writeProblem to blank.Set writeProblem to blank.orderRow in orderRows:For each orderRow in orderRows:sheetErrorIf a step below fails, save the error as sheetErrorsheetName in spreadsheetUrl. Save it as added.Add a row to sheetName in spreadsheetUrl. Save it as added.rowsWritten.Add 1 to rowsWritten.writeProblem to sheetError.message.Change writeProblem to sheetError.message.rowsAdded to rowsAdded plus rowsWritten.Change rowsAdded to rowsAdded plus rowsWritten.writeProblem isn't emptyIf writeProblem isn't emptyrowsWritten is 0If rowsWritten is 0writeProblem), so '@subject' and any emails after it were left unlabelled to be tried again next hour to the end of failures and save it as failures.Add no rows could be added to the '@sheetName' tab (writeProblem), so '@subject' and any emails after it were left unlabelled to be tried again next hour to the end of failures and save it as failures.partLabelErrorIf a step below fails, save the error as partLabelErrorreviewLabel to email email. Save it as partlyFlagged.Add label reviewLabel to email email. Save it as partlyFlagged.rowsWritten line(s) of '@subject' were added before the sheet stopped taking rows (writeProblem); the email is labelled '@reviewLabel' so the rest can be added by hand, and any emails after it are tried again next hour to the end of failures and save it as failures.Add only the first rowsWritten line(s) of '@subject' were added before the sheet stopped taking rows (writeProblem); the email is labelled '@reviewLabel' so the rest can be added by hand, and any emails after it are tried again next hour to the end of failures and save it as failures.loggedLabelErrorIf a step below fails, save the error as loggedLabelErrorloggedLabel to email email. Save it as logged.Add label loggedLabel to email email. Save it as logged.ordersLogged.Add 1 to ordersLogged.loggedLabelError.message): label it by hand, or its rows will be added again next hour to the end of failures and save it as failures.Add the rows for '@subject' were added but the '@loggedLabel' label could not be put on the email (loggedLabelError.message): label it by hand, or its rows will be added again next hour to the end of failures and save it as failures.aiFailed isn't emptyIf aiFailed isn't emptyaiFailed with , and save it as aiFailedList.Join aiFailed with , and save it as aiFailedList.aiFailedList (lastAiProblem), so they were left unlabelled to be read again next hour to the end of failures and save it as failures.Add the AI could not read aiFailedList (lastAiProblem), so they were left unlabelled to be read again next hour to the end of failures and save it as failures.reportLines to an empty list.Set reportLines to an empty list.rowsAdded row(s) from ordersLogged order email(s). to the end of reportLines and save it as reportLines.Add Added rowsAdded row(s) from ordersLogged order email(s). to the end of reportLines and save it as reportLines.reviewNotes isn't emptyIf reviewNotes isn't emptyreviewNotes with ; and save it as reviewList.Join reviewNotes with ; and save it as reviewList.reviewList. to the end of reportLines and save it as reportLines.Add Labelled '@reviewLabel' for a person to check: reviewList. to the end of reportLines and save it as reportLines.inbox.emails is greater than or equal to maxEmailsIf the length of inbox.emails is greater than or equal to maxEmailsmaxEmails emails are read per run, so more may be waiting; they are read next hour. to the end of reportLines and save it as reportLines.Add Only maxEmails emails are read per run, so more may be waiting; they are read next hour. to the end of reportLines and save it as reportLines.reportLines with a space and save it as report.Join reportLines with a space and save it as report.failures isn't emptyIf failures isn't emptyfailures with ; and save it as problemList.Join failures with ; and save it as problemList.report Needs attention: problemList.Stop and report a problem: report Needs attention: problemList.report.Finish the run: report.