Send an invoice
Issue an ad-hoc invoice and email it to a customer.
Goal
Create an invoice from line items, send it to a customer's email, and (optionally) attach the PDF. Use this for one-off billing — quotes, professional services, milestone payments — or when you want the customer to pay through a hosted page rather than charging a saved instrument. For recurring billing of a fixed plan, use subscriptions instead.
Prerequisites
- A sandbox or production API key
@easylabs/nodeinstalled- A
to_emailfor the recipient (and optionally abuyer_idlinking to a Customer)
Implementation
1. Create the invoice
Items carry description, quantity, and unit_price in the smallest currency unit. The invoice starts in DRAFT; pass collection_method to control how it gets paid once finalized.
import { createClient } from "@easylabs/node";
const easy = await createClient({ apiKey: process.env.EASY_API_KEY! });
const { data: invoice } = await easy.createInvoice({
to_email: "ada@example.com",
to_company_name: "Lovelace Labs",
buyer_id: "cus_01HXXXXXXXXXXX",
currency: "USD",
collection_method: "send_invoice", // customer pays via hosted page
due_date: "2026-06-01",
items: [
{ description: "Q2 retainer", quantity: 1, unit_price: 500_000 }, // $5,000.00
{ description: "Add-on workshop", quantity: 2, unit_price: 75_000, manual_tax_rate: 8.875 },
],
notes: "Thanks for working with us — net 30.",
auto_reminders: true,
include_payment_page_link: true,
});2. Send the email
sendInvoice finalizes the invoice (DRAFT → OPEN), delivers the email, and returns the updated record. Pass attach_pdf: true to embed the PDF.
await easy.sendInvoice(invoice.id, {
attach_pdf: true,
cc_recipients: ["billing@lovelacelabs.example"],
});To schedule the send for a future time, pass scheduled_send_at instead of calling immediately, or set it on createInvoice and skip the manual sendInvoice step.
3. Charge automatically (optional)
If you'd rather collect against the customer's default payment instrument the moment the invoice opens, pass collection_method: "charge_automatically" on create and call payInvoice (or let the platform pull on due_date):
await easy.payInvoice(invoice.id, {
// omit instrument_id to use the customer's default
idempotency_key: `invoice-${invoice.id}-attempt-1`,
});4. React to status
Listen for invoice.finalized, invoice.paid, invoice.payment_failed, invoice.voided, and invoice.marked_uncollectible. Send manual nudges with remindInvoice(invoice.id) between auto-reminders, or voidInvoice(invoice.id) to cancel an open one.
Tradeoffs
send_invoicevs.charge_automatically— picksend_invoicewhen the customer expects to pay manually (typical for B2B). Pickcharge_automaticallyonly when you have an instrument on file and the customer has agreed to be billed.- Recurring invoices (
is_recurring: true+recurrence_interval) are simpler than subscriptions but lack proration, item-level mutations, and the dunning lifecycle. Choose subscriptions when the relationship is open-ended. - Tax —
manual_tax_rateon each item is the lowest-friction option. For multi-jurisdiction tax, settax_rate_idon prices and let the platform compute totals.