Typestate
Sign in

Consultation bookings from Cal.com

Cal.com sends each booking to your backend. The client gets an intake form, a reminder the day before and a thank-you after.

For a coach or consultant who takes bookings on Cal.com.

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

Start the consultation bookings from Cal.com blueprint on Typestate

Asked you for the address of your intake page and your Cal.com booking page and the address your emails come from.

Started Consultation bookings from Cal.com: 3 record types, 3 web endpoints.

7 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. A clientbooks a call on your Cal.com page
    Cal.com eventBOOKING_CREATED
    Record a BookingSaves the client and the call, and emails the link to the intake form.saves a Clientsaves a Bookingsends 1 email
  2. The clientanswers the intake questions before the call
    Web endpointPOST /intake
    Save the Intake FormKeeps each answer with the call.saves IntakeAnswerschanges a Booking
  3. The clientmoves the call to another time
    Cal.com eventBOOKING_CANCELLED
    Record a Cancel or MoveThe call takes the new time, and the reminder goes out again.changes a Booking
  4. The backendreminds the client a day before
    TimerEvery hour at 00 past
    Remind of Tomorrow's CallsEmails each client whose call starts in the next 24 hours.sends emailschanges Bookings
  5. The backendthanks the client the day after
    TimerEvery day at 10:00, Berlin time
    Follow Up After the CallEmails the link to book the next call.sends emailschanges Bookings
  6. Youread the week ahead
    Web endpoint, your keyGET /bookings/week
    List the WeekAnswers the calls of the next 7 days, earliest first.reads only
  • Event: another service tells your backend
  • Web endpoint: your website or app calls it
  • Timer: it runs at a set time

The rule you will want to change first

A moved call keeps its client and its intake answers, and its reminder goes out again for the new time.

It lives in the action Record a Cancel or Move. 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

Clientemail, name, calls
Bookingcal uid, client email, client name, title, starts at, ends at, state, reminded, intake sent
IntakeAnswerbooking, question, answer

What it does

Record a Bookingon Cal.com event
Record a Cancel or Moveon Cal.com eventThe rule to change first
Save the Intake Formweb endpoint
Mark a Missed Callweb endpoint, your key
List the Weekweb endpoint, your key
Remind of Tomorrow's CallsEvery hour at 00 past
Follow Up After the CallEvery day at 10:00, Berlin time

What it reaches

Cal.comBook times through Cal.com and act on each booking.
ResendSend email from your own address: sign-in links, receipts, reminders.
Your website3 web endpoints

What it keeps

Client

3 fields

One person who booked a call, found by their email.

email
required, an email address, never twice
name
required, at most 200 characters
calls
required, a whole number

Only Cal.com's events save and change clients. You read them on the cockpit.

Booking

9 fields

One call, as Cal.com booked it, with its state.

cal uid
required, never twice
client email
required, an email address
client name
required, at most 200 characters
title
required, at most 300 characters
starts at
required, a date and time
ends at
required, a date and time
state
required, one of booked, held, cancelled, missed
reminded
required, yes or no
intake sent
required, yes or no

Only Cal.com's events save a booking. The actions change it: the client's own link, your key and the timers.

IntakeAnswer

3 fields

One answer of the intake form, for one booking.

booking
required
question
required, at most 500 characters
answer
required, at most 5000 characters

A client sends answers through their own link. Only your key reads them.

What it does

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

Record a BookingCal.com tells the backend about a new booking, and the client gets the intake form.

What it is given

Trigger eventtext, optional

Created attext, optional

Payloadgroup of fields, needed

What it does

  • input.payload.attendeeswhat the caller gives
  • attendeea value it works out
  • Clienta record type
  • Clientan attribute of a record or an answer
  • toa part of an email or a payment
  • Refusewhere it says no

Take the first attendee of the booking as the client.When the booking names no attendee, refuse with "The booking names no attendee".

When number of input.payload.attendees is 0

Refuse "The booking names no attendee"

attendee is the first item of the given the payload's attendees

Find the Client whose email is that attendee's email.When none matches, create a Client with that attendee's email and name, and calls set to 1.When one matches, add 1 to that Client's calls.

client is

Find the first Client where

email is attendee.email

when none carry on without one

When client holds a value

Change the Client where

id is client.id

set calls to client.calls + 1

Otherwise

Add a Client

set calls to 1

set email to attendee.email

set name to attendee.name

Create a Booking with cal_uid set to the booking's uid, client_email and client_name set to that attendee's email and name, title set to the booking's title, and starts_at and ends_at set to the booking's start and end times.

Add a Booking

set cal_uid to input.payload.uid

set client_email to attendee.email

set client_name to attendee.name

set ends_at to input.payload.endTime

set starts_at to input.payload.startTime

set title to input.payload.title

Email that attendee with the subject "Your call is booked", saying the title and the start time of the call, and the link to the intake form, which is the INTAKE_ADDRESS setting with ?booking= and the booking's uid on the end.

Send an email

to attendee.email

template "Record a Booking Email"

starts_at date and time of booking.starts_at

title booking.title

uid booking.cal_uid

value the setting INTAKE_ADDRESS

Record a Cancel or MoveCal.com tells the backend that a call was cancelled or moved, and the booking follows.

What it is given

Trigger eventtext, needed

Created attext, optional

Payloadgroup of fields, needed

What it does

  • input.triggerEventwhat the caller gives
  • old_uida value it works out
  • Bookinga record type
  • Bookingan attribute of a record or an answer
  • falsea fixed text or number
  • refusewhere it says no

When the event is BOOKING_RESCHEDULED, the booking to change is the one whose cal_uid is the old booking uid; otherwise it is the one whose cal_uid is the booking's uid.Find that Booking.When none matches, refuse with "No booking matches this change".

old_uid is

when

input.triggerEvent is "BOOKING_RESCHEDULED"

then the given the payload's rescheduleUid, or else the given the payload's uid

otherwise input.payload.uid

booking is

Find the first Booking where

cal_uid is old_uid

when none refuse "No booking matches this change"

When the event is BOOKING_RESCHEDULED, change that Booking's cal_uid to the booking's uid, its starts_at and ends_at to the new start and end times, and reminded to false.Otherwise change that Booking's state to cancelled.

When input.triggerEvent is "BOOKING_RESCHEDULED"

Change the Booking where

id is booking.id

set cal_uid to input.payload.uid

set ends_at to input.payload.endTime

set reminded to false

set starts_at to input.payload.startTime

Otherwise

Change the Booking where

id is booking.id

set state to "cancelled"

Save the Intake FormThe client answers the intake questions before the call.

Who starts it

From the webPOST /intake

What it is given

Bookingtext, needed

EmailEmail, needed

AnswersList, needed

What it does

  • input.bookingwhat the caller gives
  • bookinga value it works out
  • Bookinga record type
  • Bookingan attribute of a record or an answer
  • "booked"a fixed text or number
  • refusewhere it says no

Find the Booking whose cal_uid is the given booking and whose client_email is the given email.When none matches, refuse with "No booking matches this link".

booking is

Find the first Booking where

cal_uid is input.booking

client_email is input.email

when none refuse "No booking matches this link"

When that Booking's state is not booked, refuse with "This call is no longer booked".When that Booking's intake_sent is true, refuse with "The form for this call was sent already".

When booking.state is not "booked"

Refuse "This call is no longer booked"

When booking.intake_sent

Refuse "The form for this call was sent already"

When more than 20 answers are given, refuse with "Send at most 20 answers".

When number of input.answers is more than 20

Refuse "Send at most 20 answers"

For each given answer, create an IntakeAnswer with booking_id set to that Booking's id, and question and answer set to the given question and answer.

For each item in input.answers

Add an IntakeAnswer

set answer to item.answer

set booking_id to booking.id

set question to item.question

Change that Booking's intake_sent to true.

Change the Booking where

id is booking.id

set intake_sent to true

Answer with the count of answers saved.

Answer

saved is the number of the given answers

Mark a Missed CallYou mark a call the client did not come to.

Who starts it

From the webPOST /bookings/missed

What it is given

Bookingtext, needed

What it does

  • input.bookingwhat the caller gives
  • bookinga value it works out
  • Bookinga record type
  • Bookingan attribute of a record or an answer
  • "booked"a fixed text or number
  • nowwhat it knows without being told
  • refusewhere it says no

Find the Booking whose cal_uid is the given booking.When none matches, refuse with "No such booking".

booking is

Find the first Booking where

cal_uid is input.booking

when none refuse "No such booking"

When that Booking's state is neither booked nor held, refuse with "Only a booked call can be marked as missed".When that Booking's starts_at is after now, refuse with "This call has not started yet".

When not (booking.state is one of "booked" or "held")

Refuse "Only a booked call can be marked as missed"

When booking.starts_at is after now

Refuse "This call has not started yet"

Change that Booking's state to missed.

Change the Booking where

id is booking.id

set state to "missed"

Answer with that Booking's cal_uid as the booking and missed as the state.

Answer

booking is booking.cal_uid

state is "missed"

List the WeekYou read the calls of the next 7 days, with the clients who sent the intake form.

Who starts it

From the webGET /bookings/week

What it is given

Nothing. It needs no input.

What it does

  • week_starta value it works out
  • Bookinga record type
  • Bookingan attribute of a record or an answer
  • todaywhat it knows without being told

List the Bookings whose starts_at is from the start of today up to 7 days later, earliest first.

week_start is start of today

bookings is

Find Bookings where

starts_at {"before":{"add_days":[{"name":"week_start"},7]},"on_or_after":{"name":"week_start"}}

sorted by starts_at, lowest first

For each Booking, answer its cal_uid as the booking, its client_name, client_email, title, starts_at, state and intake_sent.

rows is

each booking of bookings as

booking is booking.cal_uid

client_email is booking.client_email

client_name is booking.client_name

intake_sent is booking.intake_sent

starts_at is booking.starts_at

state is booking.state

title is booking.title

Answer

bookings is rows

Remind of Tomorrow's CallsA day before each call, the client gets a reminder.

Who starts it

A timer, every hour at 00 past

What it is given

Nothing. It needs no input.

What it does

  • bookingsa value it works out
  • Bookinga record type
  • Bookingan attribute of a record or an answer
  • falsea fixed text or number
  • toa part of an email or a payment

List the Bookings whose state is booked, whose reminded is false, and whose starts_at is from now up to 24 hours from now.

bookings is

Find Bookings where

reminded is false

starts_at {"after":{"context":"now"},"on_or_before":{"add_hours":[{"context":"now"},24]}}

state is "booked"

For each of those Bookings, email its client_email with the subject "Your call is tomorrow", saying its title and start time, and change its reminded to true.

For each booking in bookings, at most 50

Send an email

to booking.client_email

template "Remind of Tomorrow's Calls Email"

starts_at date and time of booking.starts_at

title booking.title

Change the Booking where

id is booking.id

set reminded to true

Follow Up After the CallThe day after a call, the client gets a thank-you with the link to book the next one.

Who starts it

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

What it is given

Nothing. It needs no input.

What it does

  • bookingsa value it works out
  • Bookinga record type
  • Bookingan attribute of a record or an answer
  • 12 hoursa fixed text or number
  • nowwhat it knows without being told
  • toa part of an email or a payment

List the Bookings whose state is booked and whose ends_at is 12 hours ago or earlier.

bookings is

Find Bookings where

ends_at is on or before now - 12 hours

state is "booked"

For each of those Bookings, email its client_email with the subject "Thank you for the call", saying the BOOKING_ADDRESS setting as the link to book the next call, and change its state to held.

For each booking in bookings, at most 50

Send an email

to booking.client_email

template "Follow Up After the Call Email"

value the setting BOOKING_ADDRESS

Change the Booking where

id is booking.id

set state to "held"

What it does by itself

  • Every hour, clients with a call in the next 24 hours get their reminder.
  • Every day at 10:00, Berlin time, clients of yesterday's calls get the follow-up.

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 Cal.com's events save and change clients. You read them on the cockpit.
  • Booking: Only Cal.com's events save a booking. The actions change it: the client's own link, your key and the timers.
  • IntakeAnswer: A client sends answers through their own link. Only your key reads them.

Web endpoints

What it doesWho may callAddress
Your intake page posts the answers here.anyonePOST /intake
Only you, with your key, mark a missed call.you, with your keyPOST /bookings/missed
Only you, with your key, read the week.you, with your keyGET /bookings/week

What it cannot do yet

  • It does not book times itself. Clients book on your Cal.com page, and Cal.com tells the backend.
  • It does not build the intake page. Your site shows the questions and posts the answers to it.
  • It does not take payment for a call. Cal.com does that, if you turn it on there.

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 "Consultation bookings from Cal.com". The backend runs on Typestate and answers these web endpoints: POST /intake: Your intake page posts the answers here. Who may call: anyone. POST /bookings/missed: Only you, with your key, mark a missed call. Who may call: you, with your key. GET /bookings/week: Only you, with your key, read the week. Who may call: you, with your key. 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 the https address of the page on your site that shows the intake questions, such as https://www.example.com/intake.
  3. It asks you for your Cal.com booking page, such as https://cal.com/your-name. The follow-up email links to it.
  4. It asks you for the address emails come from, on a domain you verified at Resend, such as "Ana Coaching <hello@ana.example>".
  5. It tells you what only you can do, such as pasting your Resend key in the cockpit. A key never goes through the agent.
  6. 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 consultation bookings from Cal.com blueprint on TypestateChoose a plan

Related blueprints