Razorpay Auto-Invoicer,
Monthly invoices for overseas clients who pay through Razorpay
My first open-source project. It bills my overseas clients on the 1st of every month, emails each one an invoice with the right bank details, and tells me which invoice every transfer pays, so Razorpay can settle it.



The problem
I bill a few clients abroad every month. They pay by bank transfer into my Razorpay MoneySaver Export Account, which gives me local bank accounts in their currency and pays me out in rupees.
It works well, but every month came with the same routine:
- Create an invoice for each client in Razorpay.
- Make a PDF with the right bank details for that client's currency.
- Email it.
- When a transfer landed, dig out the matching PDF.
- Upload it in Razorpay, because Razorpay doesn't settle an export payment until it has the invoice.
None of it is hard. All of it is easy to forget, and a missed upload means money sitting unsettled.
Hands-on steps for each client, each month
The routine above, before and after the invoicer.
| How | Steps |
|---|---|
| By hand | 5 steps |
| With the invoicer | 1 step |
What I built
A small tool that does the routine for me. It runs in Docker on my own server.
A month with the invoicer
Invoices go out1st, 09:00
One Razorpay invoice per client, in their currency, and an email with a PDF that has my bank details for that currency.
The client paysAny day
They send a normal local bank transfer, with the invoice number as the reference.
The tool finds the invoiceEvery day, 10:00
It checks for new transfers, works out which invoice each one pays, and emails me that PDF.
I upload the PDFAfter the email
In Razorpay, under Pending Actions. Razorpay then settles the money in rupees.
The books are done3rd, 09:00
Last month's settlements arrive as a CSV, with the invoice number next to each payment it matched.
An invoice that gets paid
The PDF carries everything a client's accounts team needs to pay without asking: the invoice number, the period, the amount, and a box with the bank details for their currency. The invoice number is also the payment reference, which matters later.

Matching a transfer to its invoice
This is the core of it. A bank transfer doesn't arrive with an invoice attached. It arrives with an amount and, if you're lucky, a reference. The tool tries three rules, most certain first:
- The transfer was paid through the invoice's own Razorpay link.
- The invoice number appears in the transfer's reference.
- It's the only open invoice in that currency for exactly that amount.
Anything else comes to me to decide. A guess by amount is flagged, and the client gets no receipt until I confirm it. Try it with three open invoices:
Which invoice does this transfer pay?
Open invoices
- INV-202610-client-aUSD 3,500
- INV-202610-client-bUSD 500
- INV-202610-client-cUSD 500
INV-202610-client-a. The invoice number is in the reference, so this one is certain.
Setup in four files
You run one command and it writes the config for you: .env for keys and email, clients.json for who to bill and how much, bank-details.json for the bank details per currency, and a docker-compose.yml. Then:
probeshows what Razorpay returns for your account, without changing anything.invoice --dry-runpreviews every invoice and email. Nothing is created or sent.docker compose up -dstarts the schedule.


What I learned about Razorpay's API
Most of the work wasn't the invoices. It was finding out what Razorpay's API can and can't do for export payments, and building around the gaps.
- The upload has no API. Razorpay won't settle an export payment until you attach the invoice and the buyer's address in the dashboard, and there's no API for that. So the tool can't do it, but its daily check hands you the right PDF within a day.
- Bank details have no API either. The account numbers for each currency live in the MoneySaver dashboard. You copy them into
bank-details.jsononce. - Invoices stay "issued". The money arrives by bank transfer, not through the invoice link, so Razorpay never marks these invoices as paid. The tool keeps its own record of what's paid.
- No tax on foreign-currency invoices. The API can't make GST invoices or add tax to them. Exports under a Letter of Undertaking (LUT) don't charge GST anyway, so that wording goes in the invoice footer.
- Quantities must be whole numbers. 37.5 hours can't be a quantity, so fractional hours become one line with the full amount.
- How export transfers show up in the API isn't documented. So client receipts and reminders are off by default, and the README says to turn them on only after a dry run has matched a real transfer correctly.
- FIRS certificates are manual too. Razorpay emails them, but there's no API to fetch them.
Built so it can't email anyone twice
A billing tool that sends a client the same invoice twice, or a reminder for something they've paid, does more harm than good. So most of the design is about being safe to run again:
- The invoice number is the duplicate guard. It's built from the month and the client (
INV-202610-client-a) and stored with the Razorpay invoice. Before creating one, the tool looks it up. Run it ten times, get one invoice. - A small state file remembers which payments it has seen and which receipts and reminders went out. If that file is damaged, the tool stops instead of starting over, because starting over could resend everything.
- One run at a time. A lock file stops a manual run and the scheduler from writing over each other.
- The first check only takes notes. It records the payments that already exist, so turning the tool on never sends a pile of late receipts.
- Nothing goes out stale. Turning receipts on never sends them for old payments, and a reminder more than a week past its day is dropped.
- Missed the 1st? If the server was down, the tool catches up when it starts again, as long as that's within three days. Invoices that exist are skipped.
- Failed emails retry on the next run and every day after, up to four tries, then it tells me.
- Logs stay clean. They never print keys, names, addresses, bank details or amounts.
Open source from day one
It's MIT licensed, with a Docker image on GitHub's container registry, CI on every pull request, and contributing, security and code of conduct files. Node runs the TypeScript directly, so there's no build step, and there are only five runtime dependencies.
It does one job for one kind of account. If you bill clients abroad through a MoneySaver Export Account, it should save you the same monthly routine it saved me.
New to the MoneySaver Export Account? Sign up for Razorpay with my referral link.
Get it on GitHub, or if you'd like a tool like this built for your own business, see what I build or get in touch.