| name | meeting-scheduler-followup |
| description | Smart meeting scheduling with calendar availability, pre-meeting briefs from email/LinkedIn/news context, and post-meeting follow-up automation. Finds open slots, drafts scheduling emails, generates meeting prep, and sends recap notes. |
Meeting Scheduler & Follow-Up
End-to-end meeting automation: find available times, draft scheduling emails, generate pre-meeting intelligence briefs, and automate post-meeting follow-ups.
When to Use
- "Schedule a meeting with Jane from Acme next week"
- "Find a time that works for me and send an invite"
- "Prep me for my meeting with [Company] tomorrow"
- "Send follow-up notes from today's meeting with [Name]"
- "What meetings do I have this week and what should I prep for?"
- "Draft a scheduling email to [Name] with 3 time options"
Required Context (ask if not provided)
- For scheduling: Contact name + email, meeting type (intro/demo/check-in), preferred timeframe
- For prep: Which meeting (today/tomorrow/specific date), or auto-prep for all upcoming
- For follow-up: Which meeting, key discussion points (user can provide or Sai infers from context)
Prerequisites
- Google Calendar connected (required)
- Gmail connected (required for sending scheduling emails and follow-ups)
- LinkedIn access via browser (optional, for pre-meeting research)
Connect Services
await requestApproval({
reason: "Need Calendar access to check availability and create events",
type: "service_auth",
payload: { provider: "google", service: "calendar" }
})
await requestApproval({
reason: "Need Gmail access to send scheduling and follow-up emails",
type: "service_auth",
payload: { provider: "google", service: "gmail_write" }
})
Safety Rules (CRITICAL)
- Always require approval before sending calendar invites to external contacts.
- Always require approval before sending scheduling or follow-up emails.
- Never double-book: Always verify calendar before proposing any time.
- Default meeting duration: 30 minutes. Only use 60 if user specifies or meeting type is "demo".
- Include timezone in ALL time references, always.
- Never access private calendar details of other attendees.
- Business hours only: Don't propose times before 9am or after 5pm unless user specifies.
Workflow A: Smart Meeting Scheduling
Step 1: Parse Scheduling Request
var schedulingRequest = {
contact: {
name: "",
email: "",
company: ""
},
meetingType: "",
duration: 30,
preferredWindow: "",
notes: ""
}
if (schedulingRequest.meetingType === "demo") schedulingRequest.duration = 60
if (schedulingRequest.meetingType === "intro") schedulingRequest.duration = 30
if (schedulingRequest.meetingType === "check-in") schedulingRequest.duration = 30
if (schedulingRequest.meetingType === "proposal_review") schedulingRequest.duration = 45
Step 2: Find Available Slots
var windowStart, windowEnd
var now = new Date()
if (schedulingRequest.preferredWindow === "ASAP") {
windowStart = now
windowEnd = new Date(now.getTime() + 5 * 24 * 60 * 60 * 1000)
} else if (schedulingRequest.preferredWindow === "next week") {
var daysUntilMonday = (8 - now.getDay()) % 7 || 7
windowStart = new Date(now.getTime() + daysUntilMonday * 24 * 60 * 60 * 1000)
windowStart.setHours(0, 0, 0, 0)
windowEnd = new Date(windowStart.getTime() + 5 * 24 * 60 * 60 * 1000)
} else {
windowStart = now
windowEnd = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)
}
var existingEvents = await google.calendar.listEvents({
timeMin: windowStart.toISOString(),
timeMax: windowEnd.toISOString(),
maxResults: 50
})
console.log("Existing events in window: " + existingEvents.length)
var availableSlots = []
Step 3: Generate Time Options
var timeOptions = [
{
label: "Option A (Recommended)",
day: "Tuesday, Apr 15",
time: "10:00 AM",
timezone: "PDT",
reason: "Mid-week morning, highest acceptance rate"
},
{
label: "Option B",
day: "Wednesday, Apr 16",
time: "2:00 PM",
timezone: "PDT",
reason: "Mid-week afternoon slot"
},
{
label: "Option C",
day: "Thursday, Apr 17",
time: "11:00 AM",
timezone: "PDT",
reason: "Late-week option"
}
]
Step 4: Draft Scheduling Email
var schedulingEmail = {
to: schedulingRequest.contact.email,
subject: schedulingRequest.meetingType === "demo"
? "Demo: [Your Product] x [Their Company]"
: "Quick chat - [Topic]?",
body: generateSchedulingBody(schedulingRequest, timeOptions)
}
Step 5: Send and Create Event
await requestApproval({
reason: "Send scheduling email to " + schedulingRequest.contact.name +
" (" + schedulingRequest.contact.email + ") proposing 3 meeting times"
})
await google.gmail.sendMessage(schedulingEmail)
await requestApproval({
reason: "Create calendar event: " + meetingTitle + " on " + confirmedDate
})
var event = await google.calendar.createEvent({
summary: meetingTitle,
start: confirmedStartISO,
end: confirmedEndISO,
description: "Meeting with " + schedulingRequest.contact.name +
" (" + schedulingRequest.contact.company + ")\n\n" +
"Agenda:\n" + schedulingRequest.notes,
attendees: [schedulingRequest.contact.email]
})
console.log("Event created: " + event.htmlLink)
Workflow B: Pre-Meeting Intelligence Brief
Step 1: Find Upcoming Meetings
var briefWindow = 24
var upcoming = await google.calendar.listEvents({
timeMin: new Date().toISOString(),
timeMax: new Date(Date.now() + briefWindow * 60 * 60 * 1000).toISOString(),
maxResults: 10
})
var externalMeetings = upcoming.filter(event => {
return event.attendees && event.attendees.some(a =>
a.email && !a.email.includes(userDomain)
)
})
console.log("External meetings in next " + briefWindow + "h: " + externalMeetings.length)
Step 2: Gather Intelligence Per Meeting
For each external meeting, collect context from multiple sources:
for (var meeting of externalMeetings) {
var brief = {
meeting: {
title: meeting.summary,
time: meeting.start,
attendees: meeting.attendees.map(a => a.email),
meetingLink: meeting.conferenceLink || ""
},
emailContext: {},
calendarHistory: {},
linkedinContext: {},
companyNews: {},
suggestedAgenda: []
}
var contactEmail = meeting.attendees.find(a => !a.email.includes(userDomain))?.email
var contactName = contactEmail ? contactEmail.split("@")[0].replace(/[._]/g, " ") : ""
var emails = await google.gmail.listMessages({
query: "from:" + contactEmail + " OR to:" + contactEmail,
maxResults: 5
})
if (emails.messages && emails.messages.length > 0) {
var lastEmail = await google.gmail.getMessage(emails.messages[0].id)
brief.emailContext = {
lastEmailDate: lastEmail.date,
lastEmailSubject: lastEmail.subject,
lastEmailSnippet: lastEmail.snippet,
openItems: ""
}
}
var pastMeetings = await google.calendar.listEvents({
query: contactName,
timeMin: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString(),
timeMax: new Date().toISOString()
})
brief.calendarHistory = {
previousMeetings: pastMeetings.length,
lastMeetingDate: pastMeetings.length > 0 ? pastMeetings[pastMeetings.length - 1].start : "None",
lastMeetingTopic: pastMeetings.length > 0 ? pastMeetings[pastMeetings.length - 1].summary : ""
}
if (contactName) {
var page = await browser.newtab("https://www.linkedin.com/search/results/people/?keywords=" + encodeURIComponent(contactName))
await page.wait({ waitTime: 5 })
var snap = await page.snapshot()
brief.linkedinContext = {
currentTitle: "",
recentPost: "",
recentPostDate: ""
}
await page.close()
}
if (contactEmail) {
var companyDomain = contactEmail.split("@")[1].replace(".com", "").replace(".io", "")
var newsPage = await browser.newtab(
"https://www.google.com/search?q=" + encodeURIComponent(companyDomain) + "&tbm=nws&tbs=qdr:m"
)
await newsPage.wait({ waitTime: 3 })
var newsSnap = await newsPage.snapshot()
brief.companyNews = {
headlines: []
}
await newsPage.close()
}
meeting.brief = brief
}
Step 3: Generate Meeting Brief
## Meeting Brief: Jane Doe (Acme Corp)
**When:** Today at 2:00 PM PDT (in 3 hours)
**Meeting Link:** [Google Meet / Zoom link]
### Context
| Source | Details |
|--------|---------|
| Last Email | Apr 5 - "Re: Pricing tiers" - She asked about enterprise pricing |
| Last Meeting | Mar 20 (Demo) - Showed product, she was interested in API features |
| LinkedIn | VP Marketing, posted 3 days ago about "content ops at scale" |
| Company News | "Acme raises $30M Series B" (TechCrunch, Apr 8) |
### Open Items
- She asked about enterprise pricing in her last email (have an answer ready)
- She was interested in API features during the demo
### Suggested Agenda
1. Congratulate on the Series B (great rapport builder)
2. Quick recap of demo from Mar 20
3. Address enterprise pricing question
4. Discuss API integration specifics
5. Next steps + timeline
### Conversation Starters
- "Congrats on the Series B! Must be an exciting time for the team."
- "Your post about content ops really resonated - scaling that is always the fun challenge."
Workflow C: Post-Meeting Follow-Up
Step 1: Gather Meeting Context
var recentMeetings = await google.calendar.listEvents({
timeMin: new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString(),
timeMax: new Date().toISOString()
})
var targetMeeting = recentMeetings.find(m =>
m.summary.toLowerCase().includes(contactName.toLowerCase()) ||
m.attendees?.some(a => a.email?.includes(contactName))
)
Step 2: Draft Follow-Up Email
var followUpEmail = {
to: contactEmail,
subject: "Re: " + targetMeeting.summary,
body: ""
}
Step 3: Send and Track
await requestApproval({
reason: "Send meeting follow-up email to " + contactEmail +
" for the meeting: " + targetMeeting.summary
})
await google.gmail.sendMessage(followUpEmail)
console.log("Follow-up sent to " + contactEmail)
if (nextMeetingAgreed) {
console.log("Next meeting discussed - starting scheduling workflow...")
}
for (var item of actionItems) {
if (item.owner === "me" && item.dueDate) {
await google.calendar.createEvent({
summary: "ACTION: " + item.description,
start: item.dueDate,
end: item.dueDate,
description: "From meeting with " + contactEmail + " on " + meetingDate
})
}
}
Workflow D: Weekly Meeting Prep (Schedulable)
Run every Monday morning to prep for the entire week:
var weekStart = new Date()
weekStart.setHours(0, 0, 0, 0)
var weekEnd = new Date(weekStart.getTime() + 5 * 24 * 60 * 60 * 1000)
var weekMeetings = await google.calendar.listEvents({
timeMin: weekStart.toISOString(),
timeMax: weekEnd.toISOString()
})
var externalMeetings = weekMeetings.filter(m =>
m.attendees && m.attendees.some(a => !a.email?.includes(userDomain))
)
console.log("External meetings this week: " + externalMeetings.length)
Error Handling
- Calendar conflict detected: Alert user immediately, suggest alternative times
- Email send failure: Retry once, then save as draft for manual sending
- LinkedIn access limited: Skip LinkedIn context, note in brief
- Contact email not found: Ask user to provide email
- Timezone ambiguity: Default to user's calendar timezone, always confirm