How to never forget to pay an invoice in NetSuite, using Merge - Part 3
Let's write a Python script to check for past due invoices
Series
This is part 3 of a multi-part series where we:
Set up Merge to talk to our NetSuite instance, and pull invoices
Write a Python script that checks for past due invoices, and send an email about it [you are here]
Overview
Currently, Merge pulls data from NetSuite and stores it on its own servers. We need to:
Pull invoices from Merge
Filter them for past due ones
Send an email to ourselves about those past due invoices
Pull invoices
cURL
Before we jump into our python script, let’s check that Merge has the data we’re interested in using cURL
. Two things we need first:
Our access key (specific to our Merge account), which can be found here:
Our account token (specific to our Linked Account with NetSuite):
Per Merge’s API docs, we’re interested in the /invoices?type=ACCOUNTS_RECEIVABLE
endpoint. So our cURL command is then:
curl "https://api.merge.dev/api/accounting/v1/invoices?type=ACCOUNTS_PAYABLE&status=OPEN" \
-H "Authorization: Bearer ${MERGE_API_KEY}" \
-H "X-Account-Token: ${MERGE_ACCOUNT_TOKEN}"
Which returns…
{
"next": null,
"previous": null,
"results": [
{
"id": "c124acef-edf1-485c-b182-4b1acea46c81",
"remote_id": "7373",
"created_at": "2025-02-21T23:12:45.682478Z",
"modified_at": "2025-02-26T15:08:44.102739Z",
"type": "ACCOUNTS_RECEIVABLE”
}
...
}
Take that in for a second.
With a key and a token, we can interrogate every corner of NetSuite. No complex oauth auth mechanisms or obtuse rate limits. And most of all, a consistent data structure across NetSuite, and other tools like Sage / Xero / Quickbooks / etc.
Python
To get the invoices, we use our trusty requests
library to replicate the cURL
request above:
url = (
f"{MERGE_BASE_URL}/invoices"
"?type=ACCOUNTS_PAYABLE&status=OPEN"
)
response = requests.get(
url,
headers={
"Authorization": f"Bearer {MERGE_API_KEY}",
"X-Account-Token": MERGE_ACCOUNT_TOKEN,
},
)
response.raise_for_status()
decoded_response = response.json()
invoices = decoded_response["results"]
Couple interesting fields on an invoice, including:
remote_id
(this is the internal ID on NetSuite’s side)total_amount
due_date
With that in mind, we can filter for past due invoices by using the due_date
field. The remote_id might be useful to link the invoice in our email, so that recipient can click on it to view more details. Let’s put this all together:
def wrap_link(netsuite_account_id, remote_id):
return f"<a href='https://td{netsuite_account_id}.app.netsuite.com/app/accounting/transactions/vendbill.nl?id={remote_id}&whence='>{remote_id}</a>"
for invoice in invoices:
# Parse ISO format datetime string. Merge always returns in UTC
due_date = datetime.fromisoformat(invoice["due_date"])
# Compare due date with current datetime in UTC
if due_date < datetime.now(timezone.utc):
invoice_html += (
f"<li>Invoice {wrap_link(NETSUITE_ACCOUNT_ID, invoice['remote_id'])} "
f"(${int(invoice['total_amount'])}) is due on "
f"{due_date.strftime('%B %d, %Y')}</li>"
)
Putting this all together with a light SendGrid integration, we got an email!
Entire code located here.
Next
Some ideas for further exploration:
Find a place to host the script, so you don’t have to run it locally. This is an excellent candidate for a Serverless architecture (CloudFlare workers, AWS Lambda).
Seeing past due invoices are great, but sometimes they’re OK because we know the vendor and we’re buddies with them. How could we see the vendor names in the email? Pulling the
Contact
andCompany
objects from Merge could be interesting.
If you’re interested in my NetSuite services (technical or functional), I currently have availability for 2025. Please message me on LinkedIn.