Create event templates, availability slots, and an embeddable booking widget with email notifications and waitlist management.
The Lifestream Vault booking system gives you a fully featured appointment scheduler that lives alongside your documents. You configure event templates, define availability windows, and publish a booking page — all without any third-party scheduling service.
By the end of this guide you will have:
<lsv-booking> widget you can drop into any web page or React appTier requirements
calendarBookingBasic — available on Pro tier: event templates, availability slots, booking page, email notifications, guest reschedulingcalendarBookingAdvanced — available on Business tier: waitlist management, booking analytics, team round-robin scheduling, custom brandingUpgrade in Settings → Subscription to access Business features.
An event template is a reusable definition of a meeting type. Each template specifies a name, duration, optional location (video call link, office address, or phone), and a description shown to guests on the booking page.
You can create multiple templates — for example: 15-minute intro call, 60-minute project review, 90-minute workshop session.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
process.env.VAULT_EMAIL!,
process.env.VAULT_PASSWORD!,
);
const VAULT_ID = 'vault-uuid';
// Templates live on the 'booking' resource (there is no client.eventTemplates).
// Template-specific values (duration, location, color, ...) go in 'defaults'.
const introCall = await client.booking.createTemplate(VAULT_ID, {
name: '30-Minute Intro Call',
description:
'A short introductory call to discuss your project goals and how Lifestream Vault can help.',
defaults: {
durationMin: 30,
location: 'https://meet.example.com/intro',
color: '#06b6d4',
},
});
console.log('Template created:', introCall.id);
// Create a 60-minute deep-dive session
const deepDive = await client.booking.createTemplate(VAULT_ID, {
name: '60-Minute Project Review',
description:
'An in-depth session to review project progress, roadblocks, and next milestones.',
defaults: {
durationMin: 60,
location: 'https://meet.example.com/review',
color: '#10b981',
},
});
console.log('Deep-dive template created:', deepDive.id);Availability slots define windows of time when guests may book meetings. You can create recurring weekly patterns (e.g. Mondays 9 AM – 12 PM) or one-off time blocks for specific dates.
Slots are associated with a vault. When a guest books a slot, it is locked until the meeting concludes or you cancel it.
// Create a weekly availability slot — createSlot is positional: (vaultId, data)
// Monday–Friday, 9:00 AM – 5:00 PM, 30-minute bookings
const weekdaySlot = await client.booking.createSlot(VAULT_ID, {
title: '30-Minute Intro Call',
durationMin: 30,
daysOfWeek: ['mon', 'tue', 'wed', 'thu', 'fri'],
startTime: '09:00',
endTime: '17:00',
timezone: 'America/Toronto',
bufferMin: 15, // 15-min buffer between back-to-back bookings
});
console.log('Slot created:', weekdaySlot.id);
// Create a second slot for longer sessions
const deepDiveSlot = await client.booking.createSlot(VAULT_ID, {
title: '60-Minute Project Review',
durationMin: 60,
daysOfWeek: ['tue', 'thu'],
startTime: '14:00',
endTime: '16:00',
timezone: 'America/Toronto',
});
console.log('Slot created:', deepDiveSlot.id);All times are stored in UTC internally. Pass the timezone field in IANA format (e.g. America/Toronto, Europe/London) to ensure correct conversion. The guest-facing booking page displays times in the visitor's local timezone automatically.
Your booking page inherits its branding from the PublishedVault configuration — the same record that powers your published vault site. You can set a logo URL, accent color, header text, and a short bio that appears at the top of the booking form.
Via the UI: open Settings → Publishing → Branding and fill in the fields, then click Save Branding.
You can customize the booking page with your brand colors, logo, and a custom welcome message. These branding options are available in Settings → Booking → Appearance and apply to both the hosted booking page and the embeddable widget.
// Update published-vault settings — booking branding fields are applied
// to the booking page. publishVault.update is positional: (vaultId, params)
await client.publishVault.update(VAULT_ID, {
bookingLogoUrl: 'https://cdn.example.com/logo.svg',
bookingAccentColor: '#06b6d4',
bookingWelcomeMessage: 'Book time with the team — schedule a call to get started.',
hidePoweredBy: true,
});
console.log('Branding updated.');Custom branding (logo, accent color, custom header) is a Business tier feature. On the Pro tier, the booking page uses the Lifestream Vault default theme with your profile name and avatar.
The <lsv-booking> custom element lets you embed a fully functional booking form on any web page — your marketing site, documentation portal, or a React/Vue/Svelte app — without an iframe.
Load the widget script from your Lifestream Vault instance, then drop the element anywhere on the page. The widget communicates directly with the API and handles timezone detection, slot rendering, and confirmation emails automatically.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Book a Meeting</title>
<!-- Load the booking widget from your Vault instance -->
<script
src="https://vault.lifestreamdynamics.com/api/v1/public/embed/booking.js"
defer
></script>
</head>
<body>
<h1>Schedule time with us</h1>
<!--
lsv-booking attributes:
profile-slug — required: your public profile slug
vault-slug — required: your vault slug
api-url — optional: API server base URL (defaults to widget origin)
theme — optional: "light" or "dark" (default: "light")
-->
<lsv-booking
profile-slug="your-profile-slug"
vault-slug="your-vault-slug"
api-url="https://vault.lifestreamdynamics.com"
theme="light"
></lsv-booking>
</body>
</html>The widget script is served from /api/v1/public/embed/booking.js on your Vault instance and is publicly accessible without authentication. It registers the <lsv-booking> custom element and fetches available slots from the public booking API using the profile-slug and vault-slug attributes to identify the booking page. The theme attribute controls the color scheme ("light" or "dark").
The booking system sends transactional emails automatically — no SMTP configuration is required on your part. Email delivery is handled by the email-delivery.worker BullMQ worker using your Vault instance's configured mail provider.
There are 5 email notification types in the booking system:
| Event | Recipients | Description |
|---|---|---|
| Booking confirmed | Host + Guest | Sent immediately when a guest completes a booking. Includes date, time, location, and a cancel link. |
| Booking cancelled | Host + Guest | Sent when either party cancels. Includes cancellation reason if provided. |
| Booking rescheduled | Host + Guest | Sent when a booking is moved to a new time. Includes old and new time. |
| Booking reminder | Guest | Sent 24 hours before the scheduled time as a gentle reminder. |
| Waitlist joined | Guest | Sent when a guest joins the waitlist for a fully-booked slot. Includes their position in the queue. |
Email content uses your vault's branding (logo, accent color) when Business-tier custom branding is enabled. Otherwise, emails use the Lifestream Vault default template.
To view email delivery history or re-queue a failed email, go to Settings → Email Log in the admin panel.
Guests can reschedule their bookings without contacting you directly. When a booking is confirmed, the system generates a rescheduleToken — a unique, short-lived token embedded in the confirmation email as a secure link.
Clicking the link takes the guest to a self-service rescheduling page where they can pick a new time from your available slots. The old slot is released and a new confirmation email is sent to both parties.
The rescheduleToken is stored on the booking record and rotates after each use. Tokens expire after 72 hours.
// Fetch a booking — getBooking is positional: (vaultId, bookingId)
const booking = await client.booking.getBooking(VAULT_ID, 'booking-uuid');
console.log('Booking status:', booking.status);
console.log('Guest email:', booking.guestEmail);
// Reschedule using the guest's reschedule token (from the confirmation email).
// rescheduleBooking is positional: (token, newStartAt)
await client.booking.rescheduleBooking(
'guest-reschedule-token',
'2026-03-20T14:00:00Z',
);
console.log('Booking rescheduled.');When a slot is fully booked, the waitlist captures guest interest so you can fill cancellations automatically. Guests on the waitlist receive a notification when a spot opens up and have a limited window (default: 2 hours) to confirm the booking before the next guest is notified.
The waitlist is stored in the BookingWaitlist model and is processed by the booking service. Duplicate prevention ensures a guest can only join the waitlist once per slot.
// Guests join the waitlist through the PUBLIC booking page (no auth).
// joinWaitlist is positional: (profileSlug, vaultSlug, slotId, data)
const joined = await client.booking.joinWaitlist(
'your-profile-slug',
'your-vault-slug',
'slot-uuid',
{
guestEmail: 'guest@example.com',
guestName: 'Alex Kim',
startAt: '2026-03-15T14:00:00Z',
},
);
console.log('Waitlist position:', joined.position);
// Keep this token — it is how the guest later leaves the waitlist
console.log('Leave token:', joined.leaveToken);
// Host view: list the waitlist for a slot.
// getWaitlist is positional: (vaultId, slotId, params?) → { entries, total }
const waitlist = await client.booking.getWaitlist(VAULT_ID, 'slot-uuid');
console.log(`${waitlist.total} guests on the waitlist:`);
for (const entry of waitlist.entries) {
console.log(` - Position ${entry.position}: ${entry.guestEmail}`);
}
// A guest can leave the waitlist using their leave token (GDPR withdrawal)
await client.booking.leaveWaitlist(joined.leaveToken);
console.log('Guest removed from the waitlist.');Waitlist management is a Business tier feature (calendarBookingAdvanced). On Pro tier, fully-booked slots show a "sold out" message with no waitlist option. Upgrade in Settings → Subscription to enable waitlists.
The booking analytics resource gives you quantitative insight into how your scheduling page is performing. Three analytics views are available:
| View | What it shows |
|---|---|
| volume | Total bookings, cancellations, and no-shows over a date range |
| funnel | Conversion from page view → slot selected → booking completed |
| peak-times | Heatmap of popular booking hours and days of the week |
Analytics data is aggregated nightly by the usage-aggregation.worker.
// Booking analytics live on the 'booking' resource.
// getBookingAnalytics is positional: (vaultId, filters?)
// Volume analytics for February
const volume = await client.booking.getBookingAnalytics(VAULT_ID, {
view: 'volume',
from: '2026-02-01',
to: '2026-02-28',
});
console.log('Total bookings:', volume.total);
console.log('Cancellations:', volume.cancelled);
console.log('Completion rate:', `${(volume.completionRate * 100).toFixed(1)}%`);
// Funnel analytics
const funnel = await client.booking.getBookingAnalytics(VAULT_ID, {
view: 'funnel',
from: '2026-02-01',
to: '2026-02-28',
});
console.log('Page views:', funnel.pageViews);
console.log('Slot selections:', funnel.slotSelections);
console.log('Completed bookings:', funnel.completedBookings);
console.log('Funnel drop-off:', `${(funnel.dropOffRate * 100).toFixed(1)}%`);
// Peak-times heatmap
const peakTimes = await client.booking.getBookingAnalytics(VAULT_ID, {
view: 'peak-times',
});
// Returns a 7×24 matrix of booking counts by day-of-week × hour
for (const row of peakTimes.heatmap) {
console.log(`${row.dayOfWeek}: peak at ${row.peakHour}:00`);
}Booking analytics require a Business tier subscription. Calling these endpoints on a Pro or Free account returns a 403 with the message "Booking analytics require Business tier".
bufferMinutes: 15 on every slot to give yourself transition time between back-to-back meetings. This appears as unavailable time on the booking page.maxAdvanceDays field (e.g. 30) to prevent guests from booking too far in the future, keeping your calendar manageable.Availability slots are stored in UTC but displayed to guests in their detected local timezone. Always specify timezone in IANA format when creating slots — do not use UTC offset strings like +05:30 as they do not account for daylight saving time. Use date-fns-tz in integrations for safe timezone arithmetic.
Keep your booking page branding consistent with your marketing site. Use the same accent color, logo, and tagline across both. Guests who arrive from your website should feel a seamless transition into the booking flow.
The booking page shows the event template's description field below the duration. Keep it to 2–3 sentences that clearly set expectations: what the meeting covers, what the guest should prepare, and what happens after. Avoid jargon.
Before publishing your booking page, go through the complete guest flow:
Your booking page is live and ready to accept appointments. Here are some recommended next steps:
booking.confirmed, booking.cancelled, and booking.rescheduled to integrate with your CRM or Slack