Aslisite
Aslisite
Get Started
Free YouTube Tutorial Resource

Google Sheets Payment Reminder Automation Script

Never chase unpaid invoices manually again. Follow our YouTube tutorial, copy the free Google Apps Script code below, clone our pre-built Google Sheet template, and deploy your custom automatic email chaser in minutes.

Google Sheet Template
Pre-formatted invoice tracking sheet

We pre-built a dashboard structure with correctly labeled sheets ('Client_Emails' and 'Invoice_Tracking') and column systems so the automated script works instantly.

Clone Master Sheet Template
Setup Video Walkthrough

Watch YouTube Tutorial

PaymentReminder.gs

// CHANGE THIS TO YOUR BOUND SPREADSHEET ID IF NEEDED, OR LEAVE AS IS IF RUNNING CONTAINER-BOUND
"color: #569cd6; font-weight: bold;">const SPREADSHEET_ID = SpreadsheetApp.getActiveSpreadsheet().getId();

// 1. PHASE 1: DISPATCH INITIAL INVOICES FROM DRIVE
"color: #569cd6; font-weight: bold;">function sendInitialInvoices() {
  "color: #569cd6; font-weight: bold;">const sourceFolder = DriveApp.getFoldersByName("Invoices to Send").next();
  "color: #569cd6; font-weight: bold;">const targetFolder = DriveApp.getFoldersByName("Sent Invoices").next();
  "color: #569cd6; font-weight: bold;">const files = sourceFolder.getFiles();

  "color: #569cd6; font-weight: bold;">const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
  "color: #569cd6; font-weight: bold;">const customerSheet = ss.getSheetByName("Client_Emails");
  "color: #569cd6; font-weight: bold;">const trackingSheet = ss.getSheetByName("Invoice_Tracking");

  "color: #569cd6; font-weight: bold;">const customerData = customerSheet.getDataRange().getValues();

  "color: #569cd6; font-weight: bold;">while (files.hasNext()) {
    "color: #569cd6; font-weight: bold;">const file = files.next();
    "color: #569cd6; font-weight: bold;">const fileName = file.getName();

    // Split filename by underscore. Example: "INV-1001_Acme Corp.xlsx"
    "color: #569cd6; font-weight: bold;">const parts = fileName.split("_");
    "color: #569cd6; font-weight: bold;">if (parts.length < 2) continue;

    "color: #569cd6; font-weight: bold;">const invoiceNumber = parts[0].trim();
    
    // Dynamically isolate and strip out any file extension extension (e.g. .pdf, .xlsx, .docx)
    "color: #569cd6; font-weight: bold;">const rawCustomerPart = parts[1];
    "color: #569cd6; font-weight: bold;">const customer = rawCustomerPart.substring(0, rawCustomerPart.lastIndexOf('.')).trim();

    "color: #569cd6; font-weight: bold;">let toEmails = "";
    "color: #569cd6; font-weight: bold;">let ccEmails = "";

    // Lookup customer emails from Client_Emails directory
    "color: #569cd6; font-weight: bold;">for ("color: #569cd6; font-weight: bold;">let i = 1; i < customerData.length; i++) {
      "color: #569cd6; font-weight: bold;">if (customerData[i][0].trim().toLowerCase() === customer.toLowerCase()) {
        toEmails = customerData[i][1];
        ccEmails = customerData[i][2];
        break;
      }
    }

    "color: #569cd6; font-weight: bold;">if (toEmails) {
      "color: #569cd6; font-weight: bold;">const subject = "Tax Invoice Issued - " + invoiceNumber;

      "color: #569cd6; font-weight: bold;">const body =
        "Dear Customer,\n\n" +
        "Please find attached your digital invoice " + invoiceNumber + " ">for services/goods rendered.\n\n" +
        "We kindly request that you review the statement and arrange ">for payment according to the agreed credit terms.\n\n" +
        "Thank you ">for your business!\n\n" +
        "Best regards,\nAccounts Receivable Team";

      // Dispatch via Gmail API
      GmailApp.sendEmail(toEmails, subject, body, {
        attachments: [file.getBlob()],
        cc: ccEmails
      });

      // Append row initialization into the Invoice_Tracking database log
      trackingSheet.appendRow([
        invoiceNumber,
        customer,
        "color: #569cd6; font-weight: bold;">new Date(), // Current timestamp as Date Sent
        "Pending",
        0,          // Days Pending starts at 0
        0           // Reminder Level starts at 0 (Initial sent)
      ]);

      // Archive file into the storage destination folder
      file.moveTo(targetFolder);
    }
  }
}

// 2. MAINTENANCE PHASE: DYNAMIC ENGINE DAY UPDATES
"color: #569cd6; font-weight: bold;">function updateInvoicePendingDays() {
  "color: #569cd6; font-weight: bold;">const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName("Invoice_Tracking");
  "color: #569cd6; font-weight: bold;">const data = sheet.getDataRange().getValues();

  "color: #569cd6; font-weight: bold;">for ("color: #569cd6; font-weight: bold;">let i = 1; i < data.length; i++) {
    "color: #569cd6; font-weight: bold;">const dateSent = "color: #569cd6; font-weight: bold;">new Date(data[i][2]);
    "color: #569cd6; font-weight: bold;">const status = data[i][3];

    // Only track time dimensions "color: #569cd6; font-weight: bold;">if payment has not landed yet
    "color: #569cd6; font-weight: bold;">if (status === "Pending") {
      "color: #569cd6; font-weight: bold;">const differenceInMs = "color: #569cd6; font-weight: bold;">new Date() - dateSent;
      "color: #569cd6; font-weight: bold;">const daysPending = Math.floor(differenceInMs / (1000 * 60 * 60 * 24));
      
      // Write the updated days metric back to Column 5 (Column E)
      sheet.getRange(i + 1, 5).setValue(daysPending);
    }
  }
}

// 3. LIFECYCLE PHASE: AUTOMATED ESCALATION CHASER REMINDERS
"color: #569cd6; font-weight: bold;">function processInvoiceReminders() {
  "color: #569cd6; font-weight: bold;">const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
  "color: #569cd6; font-weight: bold;">const trackingSheet = ss.getSheetByName("Invoice_Tracking");
  "color: #569cd6; font-weight: bold;">const customerSheet = ss.getSheetByName("Client_Emails");

  "color: #569cd6; font-weight: bold;">const trackingData = trackingSheet.getDataRange().getValues();
  "color: #569cd6; font-weight: bold;">const customerData = customerSheet.getDataRange().getValues();

  "color: #569cd6; font-weight: bold;">for ("color: #569cd6; font-weight: bold;">let i = 1; i < trackingData.length; i++) {
    "color: #569cd6; font-weight: bold;">const invoiceNumber = trackingData[i][0];
    "color: #569cd6; font-weight: bold;">const customer      = trackingData[i][1];
    "color: #569cd6; font-weight: bold;">const dateSent      = "color: #569cd6; font-weight: bold;">new Date(trackingData[i][2]);
    "color: #569cd6; font-weight: bold;">const status        = trackingData[i][3];
    "color: #569cd6; font-weight: bold;">const currentLevel  = trackingData[i][5]; // Column F

    // Safeguard: Stop tracking execution immediately "color: #569cd6; font-weight: bold;">if invoice status is paid
    "color: #569cd6; font-weight: bold;">if (status !== "Pending") continue;

    "color: #569cd6; font-weight: bold;">const daysPending = Math.floor(("color: #569cd6; font-weight: bold;">new Date() - dateSent) / (1000 * 60 * 60 * 24));

    "color: #569cd6; font-weight: bold;">let toEmails = "";
    "color: #569cd6; font-weight: bold;">let ccEmails = "";

    // Lookup contact matrix info
    "color: #569cd6; font-weight: bold;">for ("color: #569cd6; font-weight: bold;">let j = 1; j < customerData.length; j++) {
      "color: #569cd6; font-weight: bold;">if (customerData[j][0].trim().toLowerCase() === customer.toLowerCase()) {
        toEmails = customerData[j][1];
        ccEmails = customerData[j][2];
        break;
      }
    }

    "color: #569cd6; font-weight: bold;">if (!toEmails) continue;

    "color: #569cd6; font-weight: bold;">let subject = "";
    "color: #569cd6; font-weight: bold;">let body = "";
    "color: #569cd6; font-weight: bold;">let systemNextLevel = currentLevel;

    // STAGE 1: GENTLE REMINDER (3 Days after initial drop)
    "color: #569cd6; font-weight: bold;">if (daysPending >= 3 && currentLevel == 0) {
      subject = "Friendly Reminder: Invoice Payment Due Notice - " + invoiceNumber;
      body =
        "Dear Customer,\n\n" +
        "This is a gentle notification regarding outstanding Invoice #" + invoiceNumber + " sent to you a few days ago.\n\n" +
        "Kindly verify with your accounting department ">if this item has been scheduled ">for settlement.\n\n" +
        "Warm regards,\nFinance Division";

      systemNextLevel = 1;
    }

    // STAGE 2: FORMAL OVERDUE REMINDER (7 Days after initial drop)
    "color: #569cd6; font-weight: bold;">else "color: #569cd6; font-weight: bold;">if (daysPending >= 7 && currentLevel == 1) {
      subject = "Second Notice: Overdue Balance Invoice #" + invoiceNumber;
      body =
        "Dear Customer,\n\n" +
        "We are writing to remind you that your payment ">for invoice #" + invoiceNumber + " is currently overdue.\n\n" +
        "Please look into this account status immediately and confirm back with remittance proof.\n\n" +
        "Regards,\nAccounts Team";

      systemNextLevel = 2;
    }

    // STAGE 3: FINAL COLLECTION ESCALATION (14 Days after initial drop)
    "color: #569cd6; font-weight: bold;">else "color: #569cd6; font-weight: bold;">if (daysPending >= 14 && currentLevel == 2) {
      subject = "FINAL REMINDER: Urgent Settlement Account Action Required - " + invoiceNumber;
      body =
        "Dear Customer,\n\n" +
        "Despite previous reminders, your account balance ">for invoice #" + invoiceNumber + " remains unpaid.\n\n" +
        "Please take note that ">if payment details are not reconciled within 48 hours, late service processing measures may apply.\n\n" +
        "We request your immediate intervention.\n\n" +
        "Respectfully,\nFinance Management Office";

      systemNextLevel = 3;
    }

    // If an action matches a target time boundary, distribute the email alert
    "color: #569cd6; font-weight: bold;">if (subject) {
      GmailApp.sendEmail(toEmails, subject, body, { cc: ccEmails });
      
      // Update Column 6 (Column F) tracking marker level value
      trackingSheet.getRange(i + 1, 6).setValue(systemNextLevel);
    }
  }
}

How to Deploy the Script

1
Way 1: Using Cloned Google Sheet Template (Easiest)

1. Click the Clone Master Sheet Template button above to copy the pre-formatted sheet directly into your Google Drive.

2. Open your Google Drive, go to your root directory, and create two new folders named exactly: Invoices to Send and Sent Invoices.

3. Open the cloned Google Sheet, navigate to Extensions in the top toolbar, and select Apps Script.

4. In the Apps Script code editor, select sendInitialInvoices from the dropdown menu at the top and click Run.

5. Follow the prompts to authorize permissions (select account, click Advanced, and click Go to Untitled Project (unsafe), then click Allow).

1. Create a brand new Google Sheet. Rename the first sheet tab to Client_Emails and create a second tab named Invoice_Tracking.

2. In the Client_Emails tab, create headers in Row 1: Column A = Client Name, Column B = Client Email, and Column C = CC Email.

3. In the Invoice_Tracking tab, create headers in Row 1: Column A = Invoice Number, Column B = Client Name, Column C = Date Sent, Column D = Status, Column E = Days Pending, and Column F = Reminder Level.

4. Open your Google Drive root folder and create two new folders named: Invoices to Send and Sent Invoices.

5. Open your sheet, click Extensions > Apps Script. Delete any starter template code, paste the code copied from this page, and click the Save icon (Ctrl + S).

6. Select sendInitialInvoices from the dropdown, click Run, and complete the authorization dialog permissions.

To automate the whole workflow, click on the **alarm clock icon (Triggers)** on the left sidebar of the Apps Script dashboard. Add the following three triggers:

1. Dispatching New Invoices:
- Function to run: sendInitialInvoices
- Event source: Time-driven
- Trigger type: Hour timer / Every hour (checks for new files in your Drive folder and emails them automatically).

2. Updating Days Elapsed:
- Function to run: updateInvoicePendingDays
- Event source: Time-driven
- Trigger type: Day timer / Midnight to 1am (increments the days counter in the tracking sheet every night).

3. Sending Escalation Reminders:
- Function to run: processInvoiceReminders
- Event source: Time-driven
- Trigger type: Day timer / 9am to 10am (checks overdue invoices daily and automatically dispatches escalation reminder emails).

Click **Save** on each trigger, and your reminder system is fully automated on autopilot!