Spots emails asking for a meeting, finds times your calendar is free, and replies offering them.

UsesGmailGoogle CalendarOpenAI

Reply to meeting requests with open times

Every 15 minutes, the unread mail in your inbox is read and each message is checked to see whether it is asking to arrange a meeting that still needs a time. For each one that is, your calendar is looked through weekday by weekday at the times of day you like to meet, the first few free slots are kept, and a short reply offering exactly those times is sent in the original thread.

You end up with meeting requests answered within minutes with real options from your calendar. Only slots the calendar reported as free are offered, and the times come from the calendar rather than from the AI, so nothing is proposed on top of an existing commitment. Every message dealt with is labelled 'Meeting-times-checked' and skipped next time, so nobody gets two replies. A message that could not be read, classified or answered is logged and left unlabelled for the next run, and if your calendar is full at every time you offer, the run stops rather than guessing.

ScheduleRun every 15 minutes.Run every 15 minutes.
ValuesSet yourName to blank.Set yourName to blank.
ValuesSet timeZone to blank.Set timeZone to blank.
ValuesSet dailyMeetingTimes to 10:00, 14:00, 16:00.Set dailyMeetingTimes to 10:00, 14:00, 16:00.
ValuesSet meetingLengthMinutes to 30.Set meetingLengthMinutes to 30.
ValuesSet daysToLookAhead to 10.Set daysToLookAhead to 10.
ValuesSet timesToOffer to 3.Set timesToOffer to 3.
ValuesSet maxEmails to 15.Set maxEmails to 15.
ValuesSet handledLabel to Meeting-times-checked.Set handledLabel to Meeting-times-checked.
ValuesSet calendarId to primary.Set calendarId to primary.
GmailGmailList emails matching '-label:@handledLabel'. Save it as inbox.List emails matching '-label:@handledLabel'. Save it as inbox.
ValuesSet meetingRequests to an empty list.Set meetingRequests to an empty list.
Decisions & LoopsFor each email in inbox.emails:For each email in inbox.emails:
ValuesSet messageId to blank.Set messageId to blank.
ValuesSet subjectText to blank.Set subjectText to blank.
ValuesSet messageText to blank.Set messageText to blank.
Decisions & LoopsIf a step below fails, save the error as readErrorIf a step below fails, save the error as readError
Try
TextTrim email.id and save it as messageId.Trim email.id and save it as messageId.
TextTrim email.subject and save it as subjectText.Trim email.subject and save it as subjectText.
TextTrim email.body and save it as messageText.Trim email.body and save it as messageText.
On error
ValuesShow in the output: Skipped an email that is missing its id, subject or body: readError as text.Show in the output: Skipped an email that is missing its id, subject or body: readError as text.
Decisions & LoopsIf messageId is empty or messageText is emptyIf messageId is empty or messageText is empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet verdict to blank.Set verdict to blank.
Decisions & LoopsIf a step below fails, save the error as classifyErrorIf a step below fails, save the error as classifyError
Try
OpenAIOpenAIAsk OpenAI: 'Decide whether this email is asking to arrange a meeting, call or appointment with yourName that still needs a time agreed.Ask OpenAI: 'Decide whether this email is asking to arrange a meeting, call or appointment with yourName that still needs a time agreed. Reply with exactly one word: yes or no. Reply no if a time has already been agreed, if the message only confirms, moves or cancels an existing meeting, or if it is a newsletter, receipt, notification or marketing email. Subject: subjectText messageText'. Save it as classification.
TextMake classification.response_text lowercase and save it as verdict.Make classification.response_text lowercase and save it as verdict.
On error
ValuesShow in the output: Could not tell whether @subjectText is a meeting request, so it was left for the next run: classifyError as text.Show in the output: Could not tell whether @subjectText is a meeting request, so it was left for the next run: classifyError as text.
Decisions & LoopsIf verdict isn't emptyIf verdict isn't empty
Yes
Decisions & LoopsIf verdict starts with yesIf verdict starts with yes
Yes
ListsAdd a record of id, subject, and text to the end of meetingRequests and save it as meetingRequests.Add a record of id, subject, and text to the end of meetingRequests and save it as meetingRequests.
No
GmailGmailAdd label handledLabel to email messageId.Add label handledLabel to email messageId.
No
No steps
Decisions & LoopsIf meetingRequests isn't emptyIf meetingRequests isn't empty
Yes
ValuesSet openSlots to an empty list.Set openSlots to an empty list.
ValuesSet dayOffset to 1.Set dayOffset to 1.
Decisions & LoopsWhile dayOffset is less than or equal to daysToLookAhead and the length of…:While dayOffset is less than or equal to daysToLookAhead and the length of…:
Lists Get the first item of today's date in timeZone shifted by dayOffset days split by T and save it as day. Get the first item of today's date in timeZone shifted by dayOffset days split by T and save it as day.
Decisions & Loops If not day is a weekend If not day is a weekend
Yes
Decisions & LoopsFor each slotTime in dailyMeetingTimes split by ,:For each slotTime in dailyMeetingTimes split by ,:
Decisions & Loops If the length of openSlots is greater than or equal to timesToOffer If the length of openSlots is greater than or equal to timesToOffer
Yes
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
Text Trim slotTime and save it as clockTime. Trim slotTime and save it as clockTime.
Decisions & Loops If clockTime is empty If clockTime is empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Values Set startsAt to dayT@clockTime. Set startsAt to dayT@clockTime.
Lists Get the first item of startsAt shifted by meetingLengthMinutes minutes split by . and save it as endsAt. Get the first item of startsAt shifted by meetingLengthMinutes minutes split by . and save it as endsAt.
Google CalendarGoogle Calendar Check if the calendar is free from startsAt to endsAt. Save it as availability. Check if the calendar is free from startsAt to endsAt. Save it as availability.
Decisions & Loops If availability.isAvailable is yes If availability.isAvailable is yes
Yes
ListsAdd the weekday name of day, day at clockTime to the end of openSlots and save it as openSlots.Add the weekday name of day, day at clockTime to the end of openSlots and save it as openSlots.
No
No steps
No
No steps
Values Add 1 to dayOffset. Add 1 to dayOffset.
Decisions & LoopsIf openSlots is emptyIf openSlots is empty
Yes
Decisions & LoopsStop and report a problem: Your calendar is busy at every time in the 'dailyMeetingTimes' step for the next daysToLookAhead days, so no times were offered. Add more times or raise 'daysToLookAhead', then run it again.Stop and report a problem: Your calendar is busy at every time in the 'dailyMeetingTimes' step for the next daysToLookAhead days, so no times were offered. Add more times or raise 'daysToLookAhead', then run it again.
No
No steps
ValuesSet offeredTimes to - openSlots joined by "Set offeredTimes to - openSlots joined by - .
Decisions & LoopsFor each request in meetingRequests:For each request in meetingRequests:
Values Set replyBody to blank. Set replyBody to blank.
Decisions & Loops If a step below fails, save the error as draftError If a step below fails, save the error as draftError
Try
OpenAIOpenAIAsk OpenAI: 'Write the plain-text body of a short, friendly reply to the email below, on behalf of yourName.Ask OpenAI: 'Write the plain-text body of a short, friendly reply to the email below, on behalf of yourName. Offer these times and ask the sender to pick one, or to suggest another time if none of them work. Use only these times - do not invent, move or add any others, and do not convert them to another time zone. Times to offer (all times timeZone): offeredTimes Each meeting is meetingLengthMinutes minutes long. Write the reply body only - no subject line, no quoted text and no placeholders to fill in. Sign off as yourName. Subject: request.subject request.text'. Save it as draft.
TextTrim draft.response_text and save it as replyBody.Trim draft.response_text and save it as replyBody.
On error
ValuesShow in the output: Could not draft a reply to @request.subject, so it was left for the next run: draftError as text.Show in the output: Could not draft a reply to @request.subject, so it was left for the next run: draftError as text.
Decisions & Loops If replyBody isn't empty If replyBody isn't empty
Yes
GmailGmailReply to email request.Reply to email request.replyBody
GmailGmailAdd label handledLabel to email request.Add label handledLabel to email request.
No
No steps
No
No steps