Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The NetSuite Pro

The NetSuite Pro Logo The NetSuite Pro Logo

The NetSuite Pro Navigation

  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask A Question
  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us
Home/ Real-World NetSuite Examples/Auto-Generate Dunning Emails by Aging Bucket (SuiteScript 2.1)

Auto-Generate Dunning Emails by Aging Bucket (SuiteScript 2.1)

πŸ’Ό Business Problem

Many finance teams still manually send overdue invoice reminders to customers. This leads to inconsistent follow-ups, cash flow delays, and missed collections.

NetSuite already provides aging reports, but there’s no built-in automated dunning email system unless you use the Advanced Dunning feature (available in SuiteBilling). For many businesses, a simple scripted automation can achieve 90% of that power with no extra licensing.

Goal:
Automatically send email reminders to customers with overdue balances based on their aging bucket (e.g., 30, 60, 90+ days) and include a summary or attached invoices for easy payment.

🧠 Approach

  1. Saved Search finds customers with open invoices and overdue balances.
  2. Map/Reduce Script (or Scheduled Script) loops through results and groups invoices by customer.
  3. Based on aging bucket (1–30, 31–60, 61–90, >90), the script:
    • Chooses an email template (polite β†’ firm).
    • Optionally attaches overdue invoices as PDFs.
    • Sends the email from a subsidiary-specific sender.
    • Updates a custom field like custentity_last_dunning_date.
  4. Optional: log summary in a custom Dunning Log record for audit trail.

🧾 Saved Search Example

Name: Overdue Invoices by Aging Bucket
Type: Transaction

Filters:

  • Type = Invoice
  • Status = Open
  • Amount Remaining > 0
  • Main Line = T

Results:

  • Customer Internal ID
  • Customer Email
  • Aging (Days Overdue)
  • Amount Remaining
  • Tran ID
  • Due Date
  • Subsidiary

Search ID: customsearch_overdue_invoices_aging


🧩 SuiteScript 2.1 β€” Map/Reduce Example

/**
 * @NApiVersion 2.1
 * @NScriptType MapReduceScript
 * Title: MR | Auto-Dunning Emails by Aging Bucket
 * Author: The NetSuite Pro
 */
define(['N/search','N/email','N/runtime','N/render','N/record','N/log'], (search, email, runtime, render, record, log) => {

  const SEARCH_ID = 'customsearch_overdue_invoices_aging';
  const PARAM_SENDER = 'custscript_dunning_sender_id';

  const getInputData = () => search.load({ id: SEARCH_ID });

  const map = (ctx) => {
    const r = JSON.parse(ctx.value);
    const custId = r.values['internalid.CUSTENTITY_CUSTOMER'] || r.values.entity?.value || null;
    const emailAddr = r.values.email || '';
    const invoiceId = r.id;
    const aging = Number(r.values.formulanumeric || r.values.daysoverdue || 0);
    ctx.write({
      key: custId,
      value: { invoiceId, emailAddr, aging }
    });
  };

  const reduce = (ctx) => {
    const custId = ctx.key;
    const data = ctx.values.map(JSON.parse);
    const recipient = data[0].emailAddr;

    if (!recipient) return;
    const agingMax = Math.max(...data.map(d => d.aging));
    const bucket = getBucket(agingMax);
    const subject = buildSubject(bucket);
    const body = buildBody(bucket, data.length);

    try {
      const sender = Number(runtime.getCurrentScript().getParameter({ name: PARAM_SENDER })) || runtime.getCurrentUser().id;

      const attachments = data.map(d => render.transaction({ entityId: d.invoiceId, printMode: render.PrintMode.PDF }));

      email.send({
        author: sender,
        recipients: recipient,
        subject,
        body,
        attachments
      });

      record.submitFields({
        type: 'customer',
        id: custId,
        values: { custentity_last_dunning_date: new Date() },
        options: { enableSourcing: false, ignoreMandatoryFields: true }
      });

      log.audit(`Dunning sent to Customer ${custId}`, { bucket, recipient });

    } catch (e) {
      log.error('Reduce Error', { custId, e });
    }
  };

  const summarize = (summary) => {
    log.audit('Dunning Summary', {
      usage: summary.usage,
      yields: summary.yields,
      concurrency: summary.concurrency
    });
  };

  // ---------- Helpers ----------
  function getBucket(days) {
    if (days <= 30) return '1-30';
    if (days <= 60) return '31-60';
    if (days <= 90) return '61-90';
    return '90+';
  }

  function buildSubject(bucket) {
    return `Payment Reminder (${bucket} Days Overdue)`;
  }

  function buildBody(bucket, count) {
    const tone = {
      '1-30': 'friendly',
      '31-60': 'gentle',
      '61-90': 'firm',
      '90+': 'urgent'
    }[bucket] || 'friendly';

    return `
      <p>Dear Customer,</p>
      <p>This is a ${tone} reminder that you have <b>${count}</b> outstanding invoice(s) now ${bucket} days past due.</p>
      <p>Please find the invoices attached and remit payment at your earliest convenience.</p>
      <p>Thank you for your prompt attention to this matter.<br/>Accounts Receivable</p>
    `;
  }

  return { getInputData, map, reduce, summarize };
});

βœ… Testing Checklist

  1. Create a few test customers with open invoices aged beyond 30, 60, 90 days.
  2. Set up a test email (e.g., your sandbox Gmail).
  3. Run the script manually β†’ confirm proper bucket and email tone.
  4. Ensure PDFs attach correctly.
  5. Add logging via custentity_last_dunning_date and verify it updates.

🧩 Optional Enhancements

FeatureDescription
Dunning Templates by SubsidiaryCreate different email tone templates by subsidiary or language.
Custom Record LogSave each sent dunning event for compliance tracking.
Aging Tiers Auto-EscalationIf the same customer appears in β€œ90+” twice, escalate to finance manager.
Payment LinksEmbed payment links directly into email (e.g., Stripe/PayPal).
Bulk PDF SummaryAttach a single PDF listing all overdue invoices instead of multiple files.
Email CC RulesAdd cc for Sales Rep or Finance Manager automatically.

πŸ“Œ Summary

GoalResult
Automatically notify customers with overdue invoicesβœ… Dunning emails sent by bucket
Include invoice PDFs and tailored messagingβœ… Branded reminders per customer
Improve AR cash flow & team efficiencyβœ… Consistent follow-up without manual effort
Share
  • Facebook

Sidebar

Ask A Question

Stats

  • Questions 6
  • Answers 6
  • Best Answers 0
  • Users 4
  • Popular
  • Answers
  • Rocky

    Issue in running a client script in NetSuite SuiteScript 2.0 ...

    • 1 Answer
  • admin

    How can I send an email with an attachment in ...

    • 1 Answer
  • admin

    How do I avoid SSS_USAGE_LIMIT_EXCEEDED in a Map/Reduce script?

    • 1 Answer
  • admin
    admin added an answer The issue is usually caused by following Wrong script file… September 14, 2025 at 10:33 pm
  • admin
    admin added an answer Steps to send an Invoice PDF by email: define(['N/email', 'N/render',… August 28, 2025 at 3:05 am
  • admin
    admin added an answer This error means your script hit NetSuite’s governance usage limit… August 28, 2025 at 3:02 am

Top Members

Rocky

Rocky

  • 1 Question
  • 22 Points
Begginer
Sophie1022

Sophie1022

  • 0 Questions
  • 20 Points
Begginer
jmargoli

jmargoli

  • 0 Questions
  • 20 Points
Begginer

Trending Tags

clientscript netsuite scripting suitescript

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Menu

  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us

Quick Links

  • NetSuite Scripting
  • NetSuite Customization
  • NetSuite Advanced PDF Template
  • NetSuite Integration
  • NetSuite Reporting & Analytics

Subscribe for NetSuite Insights....

© 2025 The NetSuite Pro. All Rights Reserved