Texts each new Airtable enquiry your Cal.com booking link in your texting hours, sends one reminder if they have not booked, then stops.

UsesAirtableCal.comTwilio

Text new enquiries a booking link, then chase once

Every hour, the workflow looks for enquiries added to your Airtable table in the last few days and texts each one your Cal.com booking link. If they still have not booked after the waiting period you choose, it sends exactly one reminder, then leaves them alone. Texts only go out between the hours you set (9:00 to 19:00 unless you change them), so nobody hears from you at 2am, and enquiries already in your table when you connect it are never texted.

Nobody who has booked is chased: before every text, Cal.com is checked for an upcoming booking under their email address. A row is marked 'Link sent' or 'Reminded' only once Twilio has accepted the text, so nobody is texted twice. A row with no usable mobile number or email address is marked 'Needs attention' for you to look at instead of being texted at a guess. If Twilio refuses a text, Cal.com cannot be checked or a row cannot be updated, the run is marked as failed and names the people affected, so a problem never looks like a quiet day.

You need Airtable, Cal.com and Twilio connected, and an Airtable table with 'Name', 'Phone', 'Email', 'Follow-up Status' and 'Follow-up Date' columns. Fill in the base and table, your Cal.com booking link, the Twilio number to text from, your business name and the country your customers' numbers are from.

ScheduleRun every hour.Run every hour.
ValuesSet airtableBaseId to blank.Set airtableBaseId to blank.
ValuesSet airtableTableName to blank.Set airtableTableName to blank.
ValuesSet bookingLink to blank.Set bookingLink to blank.
ValuesSet twilioFromNumber to blank.Set twilioFromNumber to blank.
ValuesSet businessName to blank.Set businessName to blank.
ValuesSet phoneRegion to US.Set phoneRegion to US.
ValuesSet daysToWaitBeforeReminder to 3.Set daysToWaitBeforeReminder to 3.
ValuesSet maxTextsPerRun to 12.Set maxTextsPerRun to 12.
ValuesSet calcomEventTypeId to blank.Set calcomEventTypeId to blank.
ValuesSet enquiriesToCheck to 100.Set enquiriesToCheck to 100.
ValuesSet textingHoursStart to 9.Set textingHoursStart to 9.
ValuesSet textingHoursEnd to 19.Set textingHoursEnd to 19.
ValuesSet onlyTextEnquiriesFromLastDays to 3.Set onlyTextEnquiriesFromLastDays to 3.
Dates & TimesGet today's date and save it as today.Get today's date and save it as today.
Dates & TimesGet the current date and time and save it as rightNow.Get the current date and time and save it as rightNow.
Dates & TimesGet the hour of rightNow and save it as hourNow.Get the hour of rightNow and save it as hourNow.
ValuesSet withinTextingHours to false.Set withinTextingHours to false.
Decisions & LoopsIf hourNow is greater than or equal to textingHoursStart multiplied by 1 and…If hourNow is greater than or equal to textingHoursStart multiplied by 1 and…
Yes
ValuesChange withinTextingHours to true.Change withinTextingHours to true.
No
No steps
ValuesSet invitesSent to 0.Set invitesSent to 0.
ValuesSet remindersSent to 0.Set remindersSent to 0.
ValuesSet alreadyBooked to 0.Set alreadyBooked to 0.
ValuesSet stillWaiting to 0.Set stillWaiting to 0.
ValuesSet skippedNoPhone to 0.Set skippedNoPhone to 0.
ValuesSet skippedNoEmail to 0.Set skippedNoEmail to 0.
ValuesSet skippedProblem to 0.Set skippedProblem to 0.
ValuesSet textsSent to 0.Set textsSent to 0.
ValuesSet hitTextLimit to false.Set hitTextLimit to false.
ValuesSet waitingForTextingHours to 0.Set waitingForTextingHours to 0.
ValuesSet textsTried to 0.Set textsTried to 0.
ValuesSet refusedIds to an empty list.Set refusedIds to an empty list.
ValuesSet notes to an empty list.Set notes to an empty list.
AirtableAirtableList records in airtableTableName. Save it as enquiryPage.List records in airtableTableName. Save it as enquiryPage.
ValuesSet enquiries to enquiryPage.records.Set enquiries to enquiryPage.records.
Decisions & LoopsFor each enquiry in enquiries:For each enquiry in enquiries:
Decisions & LoopsIf textsTried is greater than or equal to maxTextsPerRun multiplied by 1If textsTried is greater than or equal to maxTextsPerRun multiplied by 1
Yes
ValuesChange hitTextLimit to true.Change hitTextLimit to true.
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
ValuesSet fields to enquiry.fields.Set fields to enquiry.fields.
ValuesSet followUpStatus to blank.Set followUpStatus to blank.
Decisions & LoopsIf a step below fails, save the error as statusUnreadableIf a step below fails, save the error as statusUnreadable
Try
TextTrim fields["Follow-up Status"] and save it as followUpStatus.Trim fields["Follow-up Status"] and save it as followUpStatus.
On error
ValuesChange followUpStatus to blank.Change followUpStatus to blank.
Decisions & LoopsIf followUpStatus isn't blank and followUpStatus is not Link sentIf followUpStatus isn't blank and followUpStatus is not Link sent
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet personName to there.Set personName to there.
Decisions & LoopsIf a step below fails, save the error as nameUnreadableIf a step below fails, save the error as nameUnreadable
Try
TextTrim fields.Name and save it as personName.Trim fields.Name and save it as personName.
On error
ValuesChange personName to there.Change personName to there.
Decisions & LoopsIf personName is emptyIf personName is empty
Yes
ValuesChange personName to there.Change personName to there.
No
No steps
TextSplit personName by a space and save it as nameParts.Split personName by a space and save it as nameParts.
ValuesSet mobile to blank.Set mobile to blank.
ValuesSet mobileUsable to false.Set mobileUsable to false.
Decisions & LoopsIf a step below fails, save the error as mobileUnreadableIf a step below fails, save the error as mobileUnreadable
Try
TextTrim fields.Phone and save it as mobile.Trim fields.Phone and save it as mobile.
TextCheck whether mobile is a valid phone number and save it as mobileUsable.Check whether mobile is a valid phone number and save it as mobileUsable.
On error
ValuesChange mobileUsable to false.Change mobileUsable to false.
ValuesSet emailAddress to blank.Set emailAddress to blank.
ValuesSet emailUsable to false.Set emailUsable to false.
Decisions & LoopsIf a step below fails, save the error as emailUnreadableIf a step below fails, save the error as emailUnreadable
Try
TextTrim fields.Email and save it as emailAddress.Trim fields.Email and save it as emailAddress.
TextCheck whether emailAddress is a valid email address and save it as emailUsable.Check whether emailAddress is a valid email address and save it as emailUsable.
On error
ValuesChange emailUsable to false.Change emailUsable to false.
ValuesSet unusableReason to blank.Set unusableReason to blank.
Decisions & LoopsIf mobileUsable is noIf mobileUsable is no
Yes
ValuesAdd 1 to skippedNoPhone.Add 1 to skippedNoPhone.
ValuesChange unusableReason to no usable mobile number.Change unusableReason to no usable mobile number.
Else if @emailUsable is no
ValuesAdd 1 to skippedNoEmail.Add 1 to skippedNoEmail.
ValuesChange unusableReason to no usable email address.Change unusableReason to no usable email address.
Otherwise
No steps
Decisions & LoopsIf unusableReason isn't blankIf unusableReason isn't blank
Yes
Decisions & LoopsIf a step below fails, save the error as markProblemIf a step below fails, save the error as markProblem
Try
AirtableAirtableUpdate enquiry in airtableTableName.Update enquiry in airtableTableName.
ListsAdd Marked 'Needs attention': personName - unusableReason to the end of notes and save it as notes.Add Marked 'Needs attention': personName - unusableReason to the end of notes and save it as notes.
On error
ValuesAdd 1 to skippedProblem.Add 1 to skippedProblem.
ListsAdd Could not mark personName 'Needs attention' (unusableReason): markProblem.message to the end of notes and save it as notes.Add Could not mark personName 'Needs attention' (unusableReason): markProblem.message to the end of notes and save it as notes.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
TextFormat the phone number mobile and save it as textTo.Format the phone number mobile and save it as textTo.
TextBuild the text Hi @nameParts[0], and save it as greeting.Build the text Hi @nameParts[0], and save it as greeting.
ValuesSet inviteOpening to greetingthanks for getting in touch with businessName.Set inviteOpening to greetingthanks for getting in touch with businessName.
ValuesSet inviteText to inviteOpening. Pick a time that suits you here: bookingLink Reply STOP to opt out.Set inviteText to inviteOpening. Pick a time that suits you here: bookingLink Reply STOP to opt out.
ValuesSet reminderOpening to greetingwe have not got you booked in at businessName.Set reminderOpening to greetingwe have not got you booked in at businessName.
ValuesSet reminderText to reminderOpening yet. Here is the link again: bookingLink Reply STOP to opt out.Set reminderText to reminderOpening yet. Here is the link again: bookingLink Reply STOP to opt out.
ValuesSet textToSend to blank.Set textToSend to blank.
ValuesSet statusAfterText to blank.Set statusAfterText to blank.
Decisions & LoopsIf a step below fails, save the error as enquiryProblemIf a step below fails, save the error as enquiryProblem
Try
Cal.comCal.comList bookings. Save it as bookingLookup.List bookings. Save it as bookingLookup.
Decisions & LoopsIf bookingLookup.bookings isn't emptyIf bookingLookup.bookings isn't empty
Yes
ValuesAdd 1 to alreadyBooked.Add 1 to alreadyBooked.
AirtableAirtableUpdate enquiry in airtableTableName.Update enquiry in airtableTableName.
Else if @followUpStatus is "Link sent"
TextTrim fields["Follow-up Date"] and save it as linkSentOn.Trim fields["Follow-up Date"] and save it as linkSentOn.
Dates & TimesCount the days from linkSentOn to today and save it as daysWaited.Count the days from linkSentOn to today and save it as daysWaited.
Decisions & LoopsIf daysWaited is greater than or equal to daysToWaitBeforeReminder multiplied by…If daysWaited is greater than or equal to daysToWaitBeforeReminder multiplied by…
Yes
ValuesChange textToSend to reminderText.Change textToSend to reminderText.
ValuesChange statusAfterText to Reminded.Change statusAfterText to Reminded.
No
ValuesAdd 1 to stillWaiting.Add 1 to stillWaiting.
Otherwise
ValuesChange textToSend to inviteText.Change textToSend to inviteText.
ValuesChange statusAfterText to Link sent.Change statusAfterText to Link sent.
On error
ValuesAdd 1 to skippedProblem.Add 1 to skippedProblem.
ListsAdd Not checked, tried again next run: personName - enquiryProblem.message to the end of notes and save it as notes.Add Not checked, tried again next run: personName - enquiryProblem.message to the end of notes and save it as notes.
Decisions & LoopsIf textToSend is blankIf textToSend is blank
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf withinTextingHours is noIf withinTextingHours is no
Yes
ValuesAdd 1 to waitingForTextingHours.Add 1 to waitingForTextingHours.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesAdd 1 to textsTried.Add 1 to textsTried.
ValuesSet textWentOut to false.Set textWentOut to false.
Decisions & LoopsIf a step below fails, save the error as sendProblemIf a step below fails, save the error as sendProblem
Try
TwilioTwilioSend a text message to textTo. Save it as sentText.Send a text message to textTo. Save it as sentText.textToSend
ValuesChange textWentOut to true.Change textWentOut to true.
On error
ListsAdd enquiry.id to the end of refusedIds and save it as refusedIds.Add enquiry.id to the end of refusedIds and save it as refusedIds.
ListsAdd Twilio refused the text to personName (textTo): sendProblem.message to the end of notes and save it as notes.Add Twilio refused the text to personName (textTo): sendProblem.message to the end of notes and save it as notes.
Decisions & LoopsIf textWentOut is noIf textWentOut is no
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesAdd 1 to textsSent.Add 1 to textsSent.
Decisions & LoopsIf statusAfterText is RemindedIf statusAfterText is Reminded
Yes
ValuesAdd 1 to remindersSent.Add 1 to remindersSent.
No
ValuesAdd 1 to invitesSent.Add 1 to invitesSent.
Decisions & LoopsIf a step below fails, save the error as recordProblemIf a step below fails, save the error as recordProblem
Try
AirtableAirtableUpdate enquiry in airtableTableName.Update enquiry in airtableTableName.
On error
Decisions & LoopsStop and report a problem: Texted personName (textTo) but could not mark their row '@statusAfterText' in Airtable: recordProblem.message. Set their Follow-up Status to '@statusAfterText' by hand before the next run, or they will be texted again. Stopped here so nobody else is texted without a record.Stop and report a problem: Texted personName (textTo) but could not mark their row '@statusAfterText' in Airtable: recordProblem.message. Set their Follow-up Status to '@statusAfterText' by hand before the next run, or they will be texted again. Stopped here so nobody else is texted without a record.
ValuesSet refusedNote to blank.Set refusedNote to blank.
Decisions & LoopsIf refusedIds isn't emptyIf refusedIds isn't empty
Yes
Decisions & LoopsIf textsSent is greater than 0If textsSent is greater than 0
Yes
Decisions & LoopsFor each refusedId in refusedIds:For each refusedId in refusedIds:
Decisions & Loops If a step below fails, save the error as refusedMarkProblem If a step below fails, save the error as refusedMarkProblem
Try
AirtableAirtableUpdate refusedId in airtableTableName.Update refusedId in airtableTableName.
On error
ListsAdd Could not mark a refused text 'Needs attention': refusedMarkProblem.message to the end of notes and save it as notes.Add Could not mark a refused text 'Needs attention': refusedMarkProblem.message to the end of notes and save it as notes.
ValuesChange refusedNote to:Change refusedNote to:Other texts went out, so each refused number is marked 'Needs attention' - usually a landline, or someone who replied STOP. Fix the row and set Follow-up Status back to empty (or 'Link sent' for a reminder) to try again.
No
ValuesChange refusedNote to:Change refusedNote to:No text went out this run, which usually means a problem with your Twilio account or the 'twilioFromNumber' step. Nothing was marked, so the next run tries again.
No
No steps
ValuesSet summaryLines to [Booking links texted: invitesSent, Reminders texted: remindersSent, Already booked, marked Booked: alreadyBooked, Still inside the waiting period: stillWaiting, Due a text, held until texting hours: waitingForTextingHours, Marked 'Needs attention', no usable mobile number: skippedNoPhone, Marked 'Needs attention', no usable email address: skippedNoEmail, Texts Twilio refused: the length of refusedIds, Could not be checked: skippedProblem].Set summaryLines to [Booking links texted: invitesSent, Reminders texted: remindersSent, Already booked, marked Booked: alreadyBooked, Still inside the waiting period: stillWaiting, Due a text, held until texting hours: waitingForTextingHours, Marked 'Needs attention', no usable mobile number: skippedNoPhone, Marked 'Needs attention', no usable email address: skippedNoEmail, Texts Twilio refused: the length of refusedIds, Could not be checked: skippedProblem].
TextJoin summaryLines with a new line and save it as summary.Join summaryLines with a new line and save it as summary.
ValuesSet limitNote to blank.Set limitNote to blank.
Decisions & LoopsIf hitTextLimit is yesIf hitTextLimit is yes
Yes
ValuesChange limitNote to Change limitNote to Stopped early at this run's limit of maxTextsPerRun texts. The rest are picked up next run.
No
No steps
TextJoin notes with a new line and save it as noteList.Join notes with a new line and save it as noteList.
TextBuild the text Enquiry follow-up finished. @summary@limitNote @noteList@refusedNote and save it as report.Build the text Enquiry follow-up finished. @summary@limitNote @noteList@refusedNote and save it as report.
Decisions & LoopsIf refusedIds isn't empty or skippedProblem is greater than 0If refusedIds isn't empty or skippedProblem is greater than 0
Yes
Decisions & LoopsStop and report a problem: report.Stop and report a problem: report.
No
No steps
ValuesShow in the output: report.Show in the output: report.