MinAI - Về trang chủ
Lý thuyết
7/1345 phút
Đang tải...

Meeting Automation

Automate meeting workflows với n8n - scheduling, preparation, notes, và follow-up

🎯 Meeting Automation

Meeting Room

0

🎯 Mục tiêu bài học

TB5 min

Sau bài học này, bạn sẽ:

✅ Xây dựng smart scheduling system (Calendly-style)

✅ Tạo meeting prep automation với attendee research

✅ Implement meeting notes, transcription, và action items extraction

✅ Thiết kế post-meeting follow-up và meeting analytics

Meetings consume significant time. Bài này hướng dẫn automate toàn bộ meeting lifecycle từ scheduling đến follow-up.

1

🔍 Meeting Automation Overview

TB5 min

Full Meeting Lifecycle:

🔄Meeting Lifecycle Automation
📅Pre-Meeting
📆Scheduling
📧Calendar invites
📋Agenda creation
📄Prep materials
Reminders
🎤During Meeting
🎬Recording setup
📝Live transcription
✍️Note-taking
⏱️Time tracking
📝Post-Meeting
📊Summary generation
Action items extraction
📧Follow-up emails
📌Task creation
🎥Recording distribution

Checkpoint

Meeting automation lifecycle bao gồm mấy giai đoạn?

2

⚡ Workflow 1: Smart Scheduling

TB5 min

Calendly-Style Self-Scheduling:

Meeting Scheduler

🔗Webhook: Scheduling Request
📅Get Available Slots (Calendar + Buffer + Hours)
Return Available Times
👆User Selects Time
📆Create Calendar Event
Send Confirmations

Find Available Slots:

JavaScript
1const request = $input.item.json;
2const busySlots = $('Get Calendar').all();
3
4// Configuration
5const config = {
6 workingHours: { start: 9, end: 18 },
7 meetingDuration: request.duration || 30, // minutes
8 bufferBefore: 15, // minutes
9 bufferAfter: 15,
10 daysAhead: 14,
11 timezone: 'Asia/Ho_Chi_Minh'
12};
13
14const now = new Date();
15const slots = [];
16
17// Check next 14 days
18for (let day = 1; day <= config.daysAhead; day++) {
19 const checkDate = new Date(now);
20 checkDate.setDate(now.getDate() + day);
21
22 // Skip weekends
23 if (checkDate.getDay() === 0 || checkDate.getDay() === 6) continue;
24
25 // Check each 30-min slot
26 for (let hour = config.workingHours.start; hour < config.workingHours.end; hour++) {
27 for (let min = 0; min < 60; min += 30) {
28 const slotStart = new Date(checkDate);
29 slotStart.setHours(hour, min, 0, 0);
30
31 const slotEnd = new Date(slotStart);
32 slotEnd.setMinutes(slotEnd.getMinutes() + config.meetingDuration);
33
34 // Apply buffer
35 const bufferStart = new Date(slotStart);
36 bufferStart.setMinutes(bufferStart.getMinutes() - config.bufferBefore);
37
38 const bufferEnd = new Date(slotEnd);
39 bufferEnd.setMinutes(bufferEnd.getMinutes() + config.bufferAfter);
40
41 // Check for conflicts
42 const hasConflict = busySlots.some(busy => {
43 const busyStart = new Date(busy.json.start.dateTime);
44 const busyEnd = new Date(busy.json.end.dateTime);
45 return (bufferStart < busyEnd && bufferEnd > busyStart);
46 });
47
48 if (!hasConflict) {
49 slots.push({
50 start: slotStart.toISOString(),
51 end: slotEnd.toISOString(),
52 display: slotStart.toLocaleString('en-US', {
53 weekday: 'short',
54 month: 'short',
55 day: 'numeric',
56 hour: '2-digit',
57 minute: '2-digit'
58 })
59 });
60 }
61 }
62 }
63}
64
65return { availableSlots: slots.slice(0, 20) }; // Return top 20

Create Meeting Event:

JSON
1{
2 "name": "Create Calendar Event",
3 "type": "n8n-nodes-base.googleCalendar",
4 "parameters": {
5 "operation": "create",
6 "calendar": "primary",
7 "start": "={{ $json.selectedSlot.start }}",
8 "end": "={{ $json.selectedSlot.end }}",
9 "summary": "={{ $json.meetingType }}: {{ $json.guestName }}",
10 "description": "Scheduled via automated booking\n\nGuest: {{ $json.guestEmail }}\nTopic: {{ $json.topic }}",
11 "attendees": "={{ $json.guestEmail }}",
12 "conferenceData": {
13 "createRequest": {
14 "conferenceSolutionKey": {
15 "type": "hangoutsMeet"
16 }
17 }
18 },
19 "sendUpdates": "all"
20 }
21}

Checkpoint

Smart scheduling kiểm tra available slots trong bao nhiêu ngày?

3

🔧 Workflow 2: Meeting Prep Automation

TB5 min

Auto-Generate Prep Materials:

Meeting Prep

Trigger: Event starts in 24 hours
👥Get Attendee Info (CRM + Previous + Emails)
📄Generate Prep Document
📝Create Notion/Google Doc
📧Send Prep Reminder

Attendee Research:

JavaScript
1const event = $input.item.json;
2const attendees = event.attendees.filter(a => !a.self);
3
4const prepData = {
5 meetingTitle: event.summary,
6 meetingTime: event.start.dateTime,
7 attendees: []
8};
9
10for (const attendee of attendees) {
11 // Get CRM data (from previous node)
12 const crmData = $('Get CRM').all()
13 .find(c => c.json.email === attendee.email);
14
15 // Get previous meetings (from previous node)
16 const previousMeetings = $('Get Previous Meetings').all()
17 .filter(m => m.json.attendees?.some(a => a.email === attendee.email))
18 .slice(0, 3);
19
20 prepData.attendees.push({
21 email: attendee.email,
22 name: attendee.displayName || attendee.email.split('@')[0],
23 company: crmData?.json.company || 'Unknown',
24 role: crmData?.json.role || 'Unknown',
25 lastContact: crmData?.json.lastContact,
26 dealStage: crmData?.json.dealStage,
27 previousMeetings: previousMeetings.map(m => ({
28 date: m.json.start.dateTime,
29 topic: m.json.summary,
30 notes: m.json.description?.substring(0, 100)
31 }))
32 });
33}
34
35return prepData;

Generate Prep Document:

JavaScript
1const prep = $input.item.json;
2
3const document = `
4# Meeting Prep: ${prep.meetingTitle}
5
6 **When:** ${new Date(prep.meetingTime).toLocaleString()}
7
8## Attendees
9
10${prep.attendees.map(a => `
11### ${a.name}
12- **Company:** ${a.company}
13- **Role:** ${a.role}
14- **Deal Stage:** ${a.dealStage || 'N/A'}
15- **Last Contact:** ${a.lastContact || 'Unknown'}
16
17**Previous Meetings:**
18${a.previousMeetings.length > 0
19 ? a.previousMeetings.map(m => `- ${m.date}: ${m.topic}`).join('\n')
20 : '- No previous meetings recorded'}
21`).join('\n---\n')}
22
23## Agenda
241.
252.
263.
27
28## Questions to Ask
29-
30-
31
32## Meeting Objectives
33- [ ]
34- [ ]
35
36## Notes
37*(To be filled during meeting)*
38
39## Action Items
40*(To be filled after meeting)*
41
42*Generated automatically by n8n*
43`;
44
45return { content: document, title: `Prep: ${prep.meetingTitle}` };

Checkpoint

Meeting prep automation research attendees từ những nguồn nào?

4

🏗️ Workflow 3: Meeting Notes & Transcription

TB5 min

Auto-Create Meeting Notes:

Meeting Notes

🎤Meeting Started
📝Create Notes Document
💬Share Link via Slack
🔍After Meeting: Parse Notes
Extract Action Items
📌Create Tasks

Meeting Notes Template:

JavaScript
1const meeting = $input.item.json;
2
3const notesTemplate = `
4# ${meeting.summary}
5
6**Date:** ${new Date(meeting.start.dateTime).toLocaleString()}
7**Attendees:** ${meeting.attendees.map(a => a.email).join(', ')}
8**Location:** ${meeting.hangoutLink || meeting.location || 'N/A'}
9
10## Agenda
11${meeting.description || '*No agenda provided*'}
12
13## Discussion Notes
14
15### Topic 1:
16
17
18### Topic 2:
19
20
21### Topic 3:
22
23## Action Items
24*Format: - [ ] Task @assignee due:date*
25
26- [ ]
27- [ ]
28- [ ]
29
30## Decisions Made
31-
32
33## Next Steps
34-
35
36## Open Questions
37-
38
39*Notes auto-generated. Please update during/after meeting.*
40`;
41
42return { template: notesTemplate };

Extract Action Items:

JavaScript
1const notes = $input.item.json.content;
2
3// Parse action items
4const actionItemRegex = /- \[ \]\s*(.+?)(?:\s*@(\w+))?(?:\s*due:?(\d{1,2}\/\d{1,2}))?(?:\n|$)/gi;
5const matches = [...notes.matchAll(actionItemRegex)];
6
7const actionItems = matches.map((match, index) => ({
8 id: index + 1,
9 task: match[1].trim().replace(/@\w+/, '').replace(/due:\d{1,2}\/\d{1,2}/i, '').trim(),
10 assignee: match[2] || 'unassigned',
11 dueDate: match[3] || null,
12 source: 'meeting_notes',
13 meetingId: $input.item.json.meetingId
14}));
15
16// Parse decisions
17const decisionsRegex = /## Decisions Made\n([\s\S]*?)(?=\n---|\n##|$)/i;
18const decisionsMatch = notes.match(decisionsRegex);
19const decisions = decisionsMatch
20 ? decisionsMatch[1].split('\n').filter(d => d.trim().startsWith('-')).map(d => d.replace(/^-\s*/, ''))
21 : [];
22
23return {
24 actionItems,
25 decisions,
26 actionCount: actionItems.length,
27 decisionCount: decisions.length
28};

Checkpoint

Meeting notes template bao gồm những sections nào?

5

📊 Workflow 4: Post-Meeting Follow-Up

TB5 min

Automated Follow-Up Email:

Meeting Follow-Up

🏁Meeting Ended + Notes Submitted
🔍Parse Meeting Notes
📧Generate Summary Email
📤Send to All Attendees
📌Create Follow-Up Tasks
📅Schedule Next Meeting (if needed)

Generate Follow-Up Email:

JavaScript
1const meeting = $input.item.json;
2const notes = $('Parse Notes').item.json;
3
4const attendeeNames = meeting.attendees
5 .filter(a => !a.self)
6 .map(a => a.displayName || a.email.split('@')[0]);
7
8const emailBody = `
9Hi ${attendeeNames.length === 1 ? attendeeNames[0] : 'everyone'},
10
11Thank you for attending today's meeting: **${meeting.summary}**.
12
13## Summary
14${notes.summary || 'Meeting summary to be added.'}
15
16## Key Decisions
17${notes.decisions.length > 0
18 ? notes.decisions.map(d => `• ${d}`).join('\n')
19 : '• No formal decisions recorded'}
20
21## Action Items
22${notes.actionItems.length > 0
23 ? notes.actionItems.map(a => `• ${a.task}${a.assignee !== 'unassigned' ? ` (@${a.assignee})` : ''}${a.dueDate ? ` - Due: ${a.dueDate}` : ''}`).join('\n')
24 : '• No action items recorded'}
25
26## Next Steps
27${notes.nextSteps || 'To be determined'}
28
29 Full meeting notes: ${notes.notesUrl}
30 Recording: ${meeting.recordingUrl || 'Not available'}
31
32Please let me know if I missed anything or if you have questions.
33
34Best regards,
35${meeting.organizer}
36
37*This follow-up was automatically generated.*
38`;
39
40return {
41 to: meeting.attendees.filter(a => !a.self).map(a => a.email),
42 subject: `Follow-up: ${meeting.summary}`,
43 body: emailBody,
44 replyTo: meeting.organizer.email
45};

Checkpoint

Post-meeting follow-up email bao gồm những phần nào?

6

💡 Workflow 5: Recurring Meeting Analytics

TB5 min

Meeting Metrics Dashboard:

Meeting Analytics

Weekly Schedule
📅Get All Meetings This Week
📊Calculate Metrics
📄Generate Report
📧Send to Manager/Self

Meeting Analytics Code:

JavaScript
1const meetings = $input.all();
2
3const analytics = {
4 totalMeetings: 0,
5 totalMinutes: 0,
6 byType: {},
7 byDayOfWeek: {Mon: 0, Tue: 0, Wed: 0, Thu: 0, Fri: 0},
8 recurring: 0,
9 oneOnOne: 0,
10 largeGroup: 0,
11 longestMeeting: null,
12 shortestMeeting: null,
13 externalMeetings: 0
14};
15
16for (const meeting of meetings) {
17 const m = meeting.json;
18
19 // Skip all-day events
20 if (!m.start.dateTime) continue;
21
22 analytics.totalMeetings++;
23
24 // Duration
25 const start = new Date(m.start.dateTime);
26 const end = new Date(m.end.dateTime);
27 const duration = (end - start) / (1000 * 60);
28 analytics.totalMinutes += duration;
29
30 // Track longest/shortest
31 if (!analytics.longestMeeting || duration > analytics.longestMeeting.duration) {
32 analytics.longestMeeting = { title: m.summary, duration };
33 }
34 if (!analytics.shortestMeeting || duration < analytics.shortestMeeting.duration) {
35 analytics.shortestMeeting = { title: m.summary, duration };
36 }
37
38 // By day of week
39 const day = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][start.getDay()];
40 if (analytics.byDayOfWeek[day] !== undefined) {
41 analytics.byDayOfWeek[day]++;
42 }
43
44 // Meeting type
45 const attendeeCount = m.attendees?.length || 1;
46 if (attendeeCount === 2) {
47 analytics.oneOnOne++;
48 } else if (attendeeCount > 5) {
49 analytics.largeGroup++;
50 }
51
52 // Recurring check
53 if (m.recurringEventId) {
54 analytics.recurring++;
55 }
56
57 // External check
58 const hasExternal = m.attendees?.some(a =>
59 !a.email.includes('@yourcompany.com')
60 );
61 if (hasExternal) {
62 analytics.externalMeetings++;
63 }
64
65 // By type (from title)
66 let type = 'Other';
67 const title = m.summary.toLowerCase();
68 if (title.includes('standup')) type = 'Standup';
69 else if (title.includes('1:1') || title.includes('1-1')) type = '1:1';
70 else if (title.includes('review')) type = 'Review';
71 else if (title.includes('planning')) type = 'Planning';
72
73 analytics.byType[type] = (analytics.byType[type] || 0) + 1;
74}
75
76// Calculate derived metrics
77analytics.avgDuration = Math.round(analytics.totalMinutes / analytics.totalMeetings);
78analytics.hoursInMeetings = (analytics.totalMinutes / 60).toFixed(1);
79analytics.percentOfWeek = ((analytics.totalMinutes / (40 * 60)) * 100).toFixed(1);
80analytics.busiestDay = Object.entries(analytics.byDayOfWeek)
81 .sort((a, b) => b[1] - a[1])[0][0];
82
83return analytics;

Checkpoint

Meeting analytics tính toán percentOfWeek bằng cách nào?

7

🌟 Workflow 6: Meeting Recording Management

TB5 min

Organize Meeting Recordings:

Recording Handler

🎥New Recording Available
📋Get Recording Metadata
✏️Rename with Convention
📁Move to Team Folder
📝Generate Transcript
🔔Notify Attendees
🗑️Set Auto-Delete Date

Recording Organization:

JavaScript
1const recording = $input.item.json;
2const meeting = $('Get Meeting Details').item.json;
3
4// Generate standardized name
5const date = new Date(meeting.start.dateTime);
6const dateStr = date.toISOString().split('T')[0];
7const meetingType = detectMeetingType(meeting.summary);
8
9const newName = `${dateStr}_${meetingType}_${sanitize(meeting.summary)}`;
10
11// Determine folder based on type
12const folderMap = {
13 'Standup': 'Recordings/Standups',
14 '1:1': 'Recordings/1-on-1s',
15 'Client': 'Recordings/Client Calls',
16 'Team': 'Recordings/Team Meetings',
17 'Other': 'Recordings/General'
18};
19
20const destinationFolder = folderMap[meetingType] || folderMap['Other'];
21
22// Set retention policy
23const retentionDays = meetingType === 'Client' ? 365 : 90;
24const deleteDate = new Date();
25deleteDate.setDate(deleteDate.getDate() + retentionDays);
26
27return {
28 originalName: recording.name,
29 newName,
30 destinationFolder,
31 deleteDate: deleteDate.toISOString(),
32 attendeesToNotify: meeting.attendees.map(a => a.email),
33 metadata: {
34 meetingTitle: meeting.summary,
35 date: dateStr,
36 duration: recording.duration,
37 type: meetingType
38 }
39};
40
41function detectMeetingType(title) {
42 const t = title.toLowerCase();
43 if (t.includes('standup')) return 'Standup';
44 if (t.includes('1:1') || t.includes('1-1')) return '1:1';
45 if (t.includes('client') || t.includes('customer')) return 'Client';
46 if (t.includes('team') || t.includes('sync')) return 'Team';
47 return 'Other';
48}
49
50function sanitize(str) {
51 return str.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 50);
52}

Checkpoint

Recording organization đặt retention policy bao lâu cho client calls?

8

📋 Best Practices

TB5 min
Meeting Automation Tips

DO:

  • ✅ Send prep materials in advance
  • ✅ Follow up within 24 hours
  • ✅ Extract clear action items
  • ✅ Track meeting metrics
  • ✅ Respect calendar boundaries

DON'T:

  • ❌ Over-automate personal interactions
  • ❌ Send generic follow-ups
  • ❌ Ignore declined meetings
  • ❌ Record without consent

Checkpoint

Tại sao không nên record meeting without consent?

9

📝 Bài Tập Thực Hành

TB5 min
Meeting Automation Challenge

Build complete meeting system:

  1. Create self-scheduling workflow
  2. Build meeting prep automation
  3. Set up notes template generator
  4. Implement post-meeting follow-up
  5. Design meeting analytics dashboard

Own your meetings! 🎯

Checkpoint

Bạn đã hoàn thành những challenges nào trong bài tập?

10

🧠 Key Takeaways

TB5 min
Remember
  • 📅 Prep = Success - Automate preparation
  • 📝 Capture everything - Notes + recordings
  • Follow up fast - Within 24 hours
  • 📊 Track metrics - Know where time goes
  • 🤝 Keep it human - Automation supports, doesn't replace

Checkpoint

Tại sao nên follow up within 24 hours?

🚀 Bài tiếp theo

Onboarding Workflows — Automate new team member setup và training.