Typestate
Sign in

Freelancer invoices and reminders

Invoices get numbers that are never reused, go out by email, and a reminder follows when one is late.

For a freelancer who bills clients and chases late payments.

Resend logo Resend
Start it with your agentStarting from a blueprint does not count against the action changes of your plan.

Start the freelancer invoices and reminders blueprint on Typestate

Asked you for the name on your invoices and the address your emails come from.

Started Freelancer invoices and reminders: 2 record types, 5 web endpoints.

6 actions are compiling.

Deployed to Sandbox. Try it at your Sandbox address.

What your agent does when you start this blueprint

What runs

Each step has 3 parts: who starts it, how it reaches your backend, and the action that answers.

  1. Youadd a client
    Web endpointPOST /clients
    Add a ClientKeeps the client for your invoices.saves a Client
  2. Youdraft an invoice, and it gets the next number
    Web endpointPOST /invoices
    Draft an InvoiceNumbers the invoice and keeps it as a draft.saves an Invoice
  3. Yousend it, and the client gets it by email
    Web endpointPOST /invoices/send
    Send an InvoiceEmails the invoice to the client.sends 1 emailchanges the Invoice
  4. Your backendsends a reminder 7 days late, and again after 14
    TimerEvery day at 09:00, Berlin time
    Chase Late InvoicesReminds the client twice, then marks the invoice overdue.sends emailschanges Invoices
  5. Youmark it paid when the money arrives
    Web endpointPOST /invoices/paid
    Mark an Invoice PaidMarks the invoice paid.changes the Invoice
  • Web endpoint: your website or app calls it
  • Timer: it runs at a set time

The rule you will want to change first

A reminder goes 7 days after the due date, and a second one after 14. After that, the invoice is marked overdue and nobody is emailed again.

It lives in the action Chase Late Invoices. Tell your agent how you want it instead: one sentence changes, and you read the change before it goes Live.

What it is made of

The map shows the records it keeps, the actions it runs and the services it calls.

What it keeps

Clientowner, name, email, address
Invoiceowner, number, client, description, total, due on, state, reminders

What it does

Add a Clientweb endpoint
Draft an Invoiceweb endpoint
Send an Invoiceweb endpoint
Mark an Invoice Paidweb endpoint
List Invoicesweb endpoint
Chase Late InvoicesEvery day at 09:00, Berlin timeThe rule to change first

What it reaches

ResendSend email from your own address: sign-in links, receipts, reminders.
Your website5 web endpoints

What it keeps

Client

4 fields

A client you bill.

owner
required
name
required, at most 120 characters
email
required, an email address
address
optional, at most 400 characters

Only the freelancer who signed in sees their clients.

Invoice

8 fields

One invoice, with its number and whether it is paid.

owner
required
number
required
client
required
description
required, at most 2000 characters
total
required, a whole number
due on
required, a date
state
required, one of draft, sent, paid, overdue
reminders
required, a whole number, 0 to 2

Only the freelancer who signed in sees their invoices.

What it does

Open an action to read its steps: each sentence, with the calls that do it under it.

Add a ClientYou add a client with their email address.

Who starts it

From the webPOST /clients

What it is given

Nametext, needed

EmailEmail, needed

Addresstext, optional

What it does

  • input.addresswhat the caller gives
  • clienta value it works out
  • Clienta record type
  • Clientan attribute of a record or an answer
  • caller.idwhat it knows without being told

Create a Client with name, email and address set to the given name, email address and billing address, and owner_id set to the signed-in person.

Add a Client

set address to input.address

set email to input.email

set name to input.name

set owner_id to caller.id

Answer with that Client's id, name, email and address.

Answer

address is client.address

email is client.email

id is client.id

name is client.name

Draft an InvoiceYou write an invoice, and it gets the next number of the year.

Who starts it

From the webPOST /invoices

What it is given

Client IDtext, needed

Days to paya whole number, needed

Descriptiontext, needed

Totala whole number, needed

What it does

  • input.client_idwhat the caller gives
  • clienta value it works out
  • Clienta record type
  • Clientan attribute of a record or an answer
  • 90a fixed text or number
  • caller.idwhat it knows without being told
  • refusewhere it says no

Find the Client whose id is the given client id.When none matches, refuse with "No such client".

client is

Find the first Client where

id is input.client_id

when none refuse "No such client"

When the given days to pay are less than 1 or more than 90, refuse with "Give between 1 and 90 days to pay".

When input.days_to_pay is less than 1

or input.days_to_pay is more than 90

Refuse "Give between 1 and 90 days to pay"

The number is this year, a dash, and the count of Invoices whose owner_id is the signed-in person and whose number starts with this year, plus one, written with 3 digits: 2026-001, 2026-002.

A number never repeats inside a year, because a tax office reads the numbers as one unbroken row.

year is the first item of today as the text of a date cut at each "-"

invoices_this_year is

Count Invoices where

number starts with year

owner_id is caller.id

next is invoices_this_year + 1

number is "year-"00{the value named next as the text of a number}" when the value named next is less than 10, otherwise "0{the value named next as the text of a number}" when the value named next is less than 100, otherwise the value named next as the text of a number"

Create an Invoice with number set to that number, client_id set to the given client id, description and total_cents set to the given description and total, due_on set to that many days from today, state set to draft, reminders set to 0, and owner_id set to the signed-in person.

Add an Invoice

set client_id to client.id

set description to input.description

set due_on to today plus the given days to pay days

set number to number

set owner_id to caller.id

set reminders to 0

set state to "draft"

set total_cents to input.total

Answer with that Invoice's id, number, description, total_cents, due_on and state.

Answer

description is invoice.description

due_on is invoice.due_on

id is invoice.id

number is invoice.number

state is invoice.state

total_cents is invoice.total_cents

Send an InvoiceThe invoice goes to the client by email.

Who starts it

From the webPOST /invoices/send

What it is given

Invoice IDtext, needed

What it does

  • input.invoice_idwhat the caller gives
  • invoicea value it works out
  • Invoicea record type
  • Invoicean attribute of a record or an answer
  • "draft"a fixed text or number
  • toa part of an email or a payment
  • refusewhere it says no

Find the Invoice whose id is the given invoice id.When none matches, refuse with "No such invoice".

invoice is

Find the first Invoice where

id is input.invoice_id

when none refuse "No such invoice"

When that Invoice's state is not draft, refuse with "This invoice was already sent".

When invoice.state is not "draft"

Refuse "This invoice was already sent"

Find the Client whose id is that Invoice's client_id.

client is

Find the first Client where

id is invoice.client_id

when none carry on without one

Email that Client's email with the subject "Invoice" and that Invoice's number, from the name in the BUSINESS_NAME setting, with the description, the total in euros and the due date.

business_name is the setting BUSINESS_NAME

When client holds a value

Send an email

to client.email

template "Send an Invoice Email"

business_name business_name

description invoice.description

due_on date of invoice.due_on

number invoice.number

total_cents invoice.total_cents as money

Change that Invoice's state to sent.

Change the Invoice where

id is invoice.id

set state to "sent"

Answer with that Invoice's id, number, state and due_on.

Answer

due_on is sent.due_on

id is sent.id

number is sent.number

state is sent.state

Mark an Invoice PaidYou mark an invoice paid when the money arrives.

Who starts it

From the webPOST /invoices/paid

What it is given

Invoice IDtext, needed

What it does

  • input.invoice_idwhat the caller gives
  • invoicea value it works out
  • Invoicea record type
  • Invoicean attribute of a record or an answer
  • "draft"a fixed text or number
  • refusewhere it says no

Find the Invoice whose id is the given invoice id.When none matches, refuse with "No such invoice".

invoice is

Find the first Invoice where

id is input.invoice_id

when none refuse "No such invoice"

When that Invoice's state is draft, refuse with "Send the invoice first".

When invoice.state is "draft"

Refuse "Send the invoice first"

When that Invoice's state is paid, answer with its id, number and state, and change nothing.

When invoice.state is "paid"

Answer

id is invoice.id

number is invoice.number

state is invoice.state

Otherwise change that Invoice's state to paid, and answer with its id, number and state.

Change the Invoice where

id is invoice.id

set state to "paid"

Answer

id is paid.id

number is paid.number

state is paid.state

List InvoicesYou see every invoice, and what is owed.

Who starts it

From the webGET /invoices

What it is given

Stateone of draft, sent, paid, overdue, optional

What it does

  • input.statewhat the caller gives
  • all_invoicesa value it works out
  • Invoicea record type
  • Invoicean attribute of a record or an answer
  • "sent"a fixed text or number
  • caller.idwhat it knows without being told

List the Invoices whose owner_id is the signed-in person, newest first.When a state is given, list only the Invoices in that state.

all_invoices is

Find Invoices where

owner_id is caller.id

sorted by created_at, highest first

invoices is

each invoice of all_invoices where

input.state is not given

or invoice.state is input.state

For each Invoice, answer its number, its total_cents, its due_on and its state, and the name of the Client whose id is its client_id.

rows is

each invoice of invoices as

client_name is invoice.client.name

due_on is invoice.due_on

number is invoice.number

state is invoice.state

total_cents is invoice.total_cents

Also answer the total of total_cents over every Invoice whose owner_id is the signed-in person and whose state is sent or overdue.

owed is

Add up total_cents of Invoices where

owner_id is caller.id

state is one of "sent" or "overdue"

Answer

invoices is rows

owed_cents is owed

Chase Late InvoicesEvery morning, late invoices get a reminder: after 7 days, after 14, then they are marked overdue.

Who starts it

A timer, every day at 09:00, Berlin time

What it is given

Nothing. It needs no input.

What it does

  • invoicesa value it works out
  • Invoicea record type
  • Invoicean attribute of a record or an answer
  • "sent"a fixed text or number
  • caller.idwhat it knows without being told
  • toa part of an email or a payment

For the freelancer this run is for, list each of their Invoices whose state is sent.

invoices is

Find Invoices where

owner_id is caller.id

state is "sent"

For each of those Invoices, find the Client whose id is its client_id and who belongs to the freelancer this run is for.When none matches, go on to the next Invoice.

For each invoice in invoices, at most 50

client is

Find the first Client where

id is invoice.client_id

owner_id is caller.id

when none skip this one

When it is 7 or more days past its due_on and its reminders is 0, email that Client's email with the subject "Reminder: invoice" and the Invoice's number, with the total and the due date, and change its reminders to 1.

When days since invoice.due_on is at least 7

and invoice.reminders is 0

Send an email

to client.email

template "Chase Late Invoices Email"

due_on date of invoice.due_on

number invoice.number

total_cents invoice.total_cents as money

Change the Invoice where

id is invoice.id

set reminders to 1

When it is 14 or more days past its due_on and its reminders is 1, email that Client's email the same way, and change its reminders to 2.

When days since invoice.due_on is at least 14

and invoice.reminders is 1

Send an email

to client.email

template "Chase Late Invoices Email"

due_on date of invoice.due_on

number invoice.number

total_cents invoice.total_cents as money

Change the Invoice where

id is invoice.id

set reminders to 2

When it is more than 21 days past its due_on and its reminders is 2, change its state to overdue and email nobody.

After 2 reminders the owner takes it up by hand, so a third email would only annoy the client.

When days since invoice.due_on is more than 21

and invoice.reminders is 2

Change the Invoice where

id is invoice.id

set state to "overdue"

What it does by itself

  • Every day at 09:00, Berlin time, late invoices are chased.

Who signs in and what they may read

People sign up and sign in with an email address and a password, or with a provider you add, such as Google.

  • Client: Only the freelancer who signed in sees their clients.
  • Invoice: Only the freelancer who signed in sees their invoices.

Web endpoints

What it doesWho may callAddress
You add a client.a signed-in personPOST /clients
You draft an invoice.a signed-in personPOST /invoices
You send an invoice.a signed-in personPOST /invoices/send
You mark an invoice paid.a signed-in personPOST /invoices/paid
You read your invoices.a signed-in personGET /invoices

What it cannot do yet

  • It does not make a PDF. The email carries the invoice as text.
  • It does not take card payments. The client pays by bank transfer and you mark it paid.
  • It is not bookkeeping software. Your accountant still needs the invoices.

Build the pages with your tool

Typestate is the backend. Paste this brief into Lovable, v0, Claude Code or the tool that builds your pages.

Show the brief
Build the pages for "Freelancer invoices and reminders". The backend runs on Typestate and answers these web endpoints: POST /clients: You add a client. Who may call: a signed-in person. POST /invoices: You draft an invoice. Who may call: a signed-in person. POST /invoices/send: You send an invoice. Who may call: a signed-in person. POST /invoices/paid: You mark an invoice paid. Who may call: a signed-in person. GET /invoices: You read your invoices. Who may call: a signed-in person. People sign up at POST /auth/signup and sign in at POST /auth/login, which answers a token. Send it as Authorization: Bearer <token>. A refused call answers a sentence that can be shown to the person as it is.

Start it

  1. Tell your agent the sentence on the right.
  2. It asks you for your name or your business's name, as it goes on each invoice.
  3. It asks you for the address emails come from, on a domain you verified at Resend, such as "Riverside Physio <hello@riverside.example>".
  4. It tells you what only you can do, such as pasting your Resend key in the cockpit. A key never goes through the agent.
  5. Open your Sandbox address and try it yourself.

Starting from this blueprint does not count against the action changes of your plan. The Free plan runs it on Sandbox. Live needs a paid plan.

Start the freelancer invoices and reminders blueprint on TypestateChoose a plan

Related blueprints