How to Generate Monthly Stripe Invoices for Manual NetSuite Customers

Build a Spojit workflow that runs on the first of every month, reads your billable NetSuite customers, and creates a matching draft invoice in Stripe for each one so recurring charges go out without manual data entry.

What This Integration Does

Some customers in NetSuite are billed on a fixed recurring amount but are not attached to a Stripe subscription. Each month someone has to open NetSuite, find who is due, and hand-key an invoice into Stripe. This workflow removes that chore: a monthly schedule pulls the list of customers flagged as billable in NetSuite and, for each one, creates a draft invoice in Stripe. You review the drafts in Stripe and finalize them, so a human still signs off before money moves.

A Schedule trigger starts the run on a cron expression you control (for example the first of the month at 9am in your timezone). The workflow reads customers from NetSuite, loops over them, looks up the matching Stripe customer by the account reference you store in NetSuite, and calls Stripe to create one draft invoice per customer with auto_advance set to false so nothing is finalized automatically. Each scheduled run is independent: there is no shared state between months, so a missed or re-run month simply produces the same set of drafts again (see Common Pitfalls about idempotency).

Prerequisites

  • A connected NetSuite connection with read access to customer records. See the NetSuite connector article for setup.
  • A connected Stripe connection with permission to read customers and create invoices. See the Stripe connector article.
  • A reliable way to know which NetSuite customers are billable each month (a saved search field, a custom checkbox, or a recurring-amount field). This tutorial uses a custom body field plus an amount field read via run-suiteql.
  • A shared key between systems so you can find the right Stripe customer for each NetSuite customer. The cleanest option is to store the Stripe customer ID on the NetSuite customer record (for example in a custom field such as custentity_stripe_customer_id).

Step 1: Add the Schedule trigger

On a new workflow canvas, add a Trigger node and set its type to Schedule. A Schedule trigger uses a 5-field Unix cron expression plus an IANA timezone. To run at 9am on the first day of every month in Sydney, use:

0 9 1 * *
Australia/Sydney

The trigger output is { scheduledAt }, which you can reference later as {{ trigger.scheduledAt }} if you want to stamp the invoice description with the billing date. A single Schedule trigger can hold more than one schedule if you later need a second cadence.

Step 2: Read billable customers from NetSuite

Add a Connector node in Direct mode, choose the NetSuite connector, and select the run-suiteql tool. SuiteQL lets you filter to exactly the billable customers and return only the fields you need in one call. Set the query field to something like:

SELECT id, companyname, custentity_stripe_customer_id, custentity_monthly_amount
FROM customer
WHERE isinactive = 'F'
  AND custentity_is_billable = 'T'
  AND custentity_stripe_customer_id IS NOT NULL

Adjust the custom field names to match your account. Set limit to a sensible page size (the default is 100, max 1000) and add offset paging if you bill more customers than fit in one page. Name the output variable so you can reference the rows, for example as {{ customers.items }} (check the node output in a test run to confirm the exact path your account returns). If you prefer record CRUD over SuiteQL, the list-customers tool with a q filter is an alternative, but SuiteQL is better here because it returns custom fields and the recurring amount in a single shaped result.

Step 3: Loop over each customer

Add a Loop node set to ForEach and point it at the list from the previous step (for example {{ customers.items }}). Inside the loop body, each iteration exposes the current customer, which you can reference as {{ item }}. Everything you add inside the loop body runs once per billable customer. Keep the body lean: the next two steps (a Stripe lookup and a Stripe invoice create) belong inside this loop.

Step 4: Confirm the Stripe customer exists

Inside the loop body, add a Connector node in Direct mode, choose the Stripe connector, and select the get-customer tool. Map its customer ID input to the Stripe ID you carried over from NetSuite, for example {{ item.custentity_stripe_customer_id }}. This verifies the linked Stripe customer still exists before you try to bill them and gives you the live customer object for the description. If you would rather guard against deleted or mistyped IDs, follow this node with a Condition node that checks the lookup returned a customer, and route only matched customers into the invoice step.

Step 5: Create the draft invoice in Stripe

Still inside the loop, add a Connector node in Direct mode, choose the Stripe connector, and select the create-invoice tool. Map the fields:

  • customer: the Stripe customer ID, for example {{ item.custentity_stripe_customer_id }}.
  • auto_advance: set to false so the invoice stays a draft for review instead of finalizing and charging.
  • collection_method: pick send_invoice to email a payable invoice, or charge_automatically to charge a saved payment method.
  • description: a templated label such as Monthly charge for {{ item.companyname }} - {{ trigger.scheduledAt }}.

The create-invoice tool produces a draft invoice on the customer's account. Note that line items and amounts are managed in Stripe (via invoice items or the customer's pending charges); this workflow creates the invoice container per customer on schedule. If you bill a flat recurring amount that lives in NetSuite, store that amount as a pending invoice item in Stripe ahead of time, or keep the per-customer amount on the NetSuite record and add it as an invoice item using your Stripe configuration before this step.

Step 6: Notify the billing owner

After the loop, add a Send Email node so the person who finalizes invoices knows drafts are ready. Set Recipients to your billing inbox, a templated Subject such as Monthly Stripe drafts created - {{ trigger.scheduledAt }}, and a Body summarizing the run. Set If sending fails to Continue anyway so a mail hiccup does not fail an otherwise successful billing run. Send Email uses Spojit's built-in mail service and counts toward your monthly email allowance; external recipients must be on your org allowlist under Settings -> General -> Email recipients.

Step 7: Save and enable the schedule

Save the workflow, then enable it so the Schedule trigger becomes live. Until the workflow is enabled, the cron schedule does not fire. You can confirm the next run and review each month's outcome in the execution history. If you want a second opinion while building, open Miraxa, the intelligent layer across your automation, and ask it to explain why a run produced fewer invoices than expected or to add a Condition node that skips customers whose custentity_monthly_amount is zero.

Tips

  • Use auto_advance false on every invoice during rollout so a human reviews and finalizes each draft in Stripe. Flip it to true only once you trust the numbers.
  • Return only the columns you need in the SuiteQL query. Smaller rows keep the loop fast and make the output easier to map.
  • Set the schedule timezone explicitly (the IANA value, not a UTC offset) so the run does not drift across daylight saving changes.
  • If you bill hundreds of customers, page through NetSuite with offset and keep the loop body to the two Stripe calls to stay well within request limits.

Common Pitfalls

  • Re-running a month creates a second set of drafts because nothing checks for an existing draft. Before going live, decide your idempotency rule: keep auto_advance false so duplicates are caught at review, or add a Condition node that calls list-invoices for the customer and skips them if a draft already exists for the period.
  • A missing or stale custentity_stripe_customer_id makes create-invoice fail for that customer. The Step 4 lookup plus a Condition guard prevents one bad record from stopping the whole loop.
  • SuiteQL field names are case sensitive and account specific. Verify your custom field IDs in NetSuite before relying on the filter, and test the query against a single known customer first.
  • Custom NetSuite fields require the right permissions on the connection. If columns come back empty, your connection role may not see those fields.

Testing

Before enabling the live schedule, narrow the SuiteQL WHERE clause to a single test customer (for example add AND id = '123') and run the workflow with the Run button. Confirm the loop runs once, the Stripe lookup returns your test customer, and a single draft invoice appears in Stripe with the expected description and auto_advance false. Check the execution history to see each node's input and output. Once one customer works end to end, remove the test filter, run once more against the full list while still in draft mode, review the drafts in Stripe, and only then enable the monthly schedule.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.