Event Notifications
Event notifications allow you to programmatically trigger AI voice responses for application events, without requiring user voice input.
Overview
Use event notifications to make the AI speak about:
- Order confirmations
- Timer expiry
- New messages
- System alerts
- Status updates
- Form submissions
- Any application event
Basic Usage
typescript
import { vowel } from './vowel.client';
// Simple notification
await vowel.notifyEvent('Order placed successfully!');
// Notification with context
await vowel.notifyEvent('New message received', {
from: 'John Doe',
preview: 'Hey, are you available?',
timestamp: new Date().toISOString()
});API
notifyEvent Method
typescript
vowel.notifyEvent(
message: string,
context?: Record<string, any>
): Promise<void>Parameters:
message- The message for the AI to speak aboutcontext- Optional context object with additional information
The AI will use both the message and context to generate a natural response.
Common Patterns
Order Confirmation
typescript
async function handleOrderPlaced(order: Order) {
await processOrder(order);
await vowel.notifyEvent('Your order has been placed successfully!', {
orderId: order.id,
total: order.total,
estimatedDelivery: order.estimatedDelivery,
items: order.items.map(i => i.name)
});
}Timer Expiry
typescript
class VoiceTimer {
private timerId: number | null = null;
start(minutes: number) {
this.timerId = setTimeout(async () => {
await vowel.notifyEvent(`Your ${minutes}-minute timer has expired`);
}, minutes * 60 * 1000);
}
cancel() {
if (this.timerId) {
clearTimeout(this.timerId);
}
}
}
// Usage
const timer = new VoiceTimer();
timer.start(5); // 5-minute timerNew Message Notification
typescript
// WebSocket listener
socket.on('new-message', async (message) => {
await vowel.notifyEvent('You have a new message', {
from: message.sender.name,
preview: message.text.substring(0, 50),
timestamp: message.timestamp
});
});Shopping Cart Updates
typescript
async function addToCart(product: Product, quantity: number) {
cart.add(product, quantity);
await vowel.notifyEvent('Item added to cart', {
productName: product.name,
quantity,
cartTotal: cart.items.length,
subtotal: cart.getSubtotal()
});
}Form Submission
typescript
async function handleFormSubmit(formData: FormData) {
try {
await submitForm(formData);
await vowel.notifyEvent('Form submitted successfully!', {
formType: 'contact',
timestamp: new Date().toISOString()
});
} catch (error) {
await vowel.notifyEvent('Form submission failed', {
error: error.message
});
}
}System Alerts
typescript
// Monitor system status
async function checkSystemHealth() {
const health = await getSystemHealth();
if (health.status === 'degraded') {
await vowel.notifyEvent('System performance is degraded', {
affectedServices: health.affectedServices,
severity: 'warning'
});
}
}Advanced Patterns
Debounced Notifications
Prevent notification spam:
typescript
import { debounce } from 'lodash';
const notifyCartUpdate = debounce(async (cart: Cart) => {
await vowel.notifyEvent('Cart updated', {
itemCount: cart.items.length,
total: cart.getTotal()
});
}, 1000); // Wait 1 second after last update
// Usage
function updateCart(product: Product) {
cart.add(product);
notifyCartUpdate(cart);
}Conditional Notifications
Only notify based on user preferences:
typescript
async function notifyIfEnabled(event: string, context?: any) {
const preferences = await getUserPreferences();
if (preferences.voiceNotifications) {
await vowel.notifyEvent(event, context);
}
}
// Usage
await notifyIfEnabled('Order placed', { orderId: '12345' });Priority Notifications
Handle urgent vs. non-urgent notifications:
typescript
enum NotificationPriority {
LOW = 'low',
NORMAL = 'normal',
HIGH = 'high',
URGENT = 'urgent'
}
async function notify(
message: string,
priority: NotificationPriority,
context?: any
) {
const prefixes = {
[NotificationPriority.LOW]: '',
[NotificationPriority.NORMAL]: '',
[NotificationPriority.HIGH]: 'Important: ',
[NotificationPriority.URGENT]: 'Urgent: '
};
await vowel.notifyEvent(prefixes[priority] + message, {
...context,
priority
});
}
// Usage
await notify('System maintenance in 5 minutes', NotificationPriority.HIGH);
await notify('Critical error occurred', NotificationPriority.URGENT);Queued Notifications
Queue notifications to prevent overlapping speech:
typescript
class NotificationQueue {
private queue: Array<{ message: string; context?: any }> = [];
private processing = false;
async add(message: string, context?: any) {
this.queue.push({ message, context });
if (!this.processing) {
await this.process();
}
}
private async process() {
this.processing = true;
while (this.queue.length > 0) {
const notification = this.queue.shift()!;
await vowel.notifyEvent(notification.message, notification.context);
// Wait for AI to finish speaking
await this.waitForSpeechEnd();
}
this.processing = false;
}
private async waitForSpeechEnd(): Promise<void> {
return new Promise((resolve) => {
const checkSpeaking = () => {
if (!vowel.isAISpeaking()) {
resolve();
} else {
setTimeout(checkSpeaking, 100);
}
};
checkSpeaking();
});
}
}
// Usage
const notificationQueue = new NotificationQueue();
await notificationQueue.add('First notification');
await notificationQueue.add('Second notification');
await notificationQueue.add('Third notification');React Integration
Hook for Event Notifications
tsx
import { useVowel } from '@vowel.to/client/react';
import { useCallback } from 'react';
export function useVoiceNotification() {
const { client } = useVowel();
const notify = useCallback(async (message: string, context?: any) => {
if (client.state.isConnected) {
await client.notifyEvent(message, context);
}
}, [client]);
return { notify };
}
// Usage in component
function OrderConfirmation({ order }: { order: Order }) {
const { notify } = useVoiceNotification();
useEffect(() => {
notify('Order confirmed!', {
orderId: order.id,
total: order.total
});
}, [order, notify]);
return <div>Order #{order.id} confirmed!</div>;
}Component-Based Notifications
tsx
function NotificationTrigger({ message, context }: {
message: string;
context?: any;
}) {
const { notify } = useVoiceNotification();
useEffect(() => {
notify(message, context);
}, [message, context, notify]);
return null;
}
// Usage
<NotificationTrigger
message="Welcome to our store!"
context={{ userName: user.name }}
/>Error Handling
Always handle errors when sending notifications:
typescript
async function safeNotify(message: string, context?: any) {
try {
await vowel.notifyEvent(message, context);
} catch (error) {
console.error('Failed to send voice notification:', error);
// Fallback to visual notification
showToast(message);
}
}Best Practices
- Keep Messages Concise - Voice notifications should be brief and clear
- Provide Context - Include relevant data in the context object
- Don't Spam - Use debouncing for frequent events
- Check Connection - Verify session is active before notifying
- Handle Errors - Always have a fallback for failed notifications
- Respect Preferences - Allow users to disable voice notifications
- Test Notifications - Test with actual voice output
- Consider Timing - Don't interrupt important user actions
When to Use Event Notifications
Good Use Cases:
- ✅ Order confirmations
- ✅ Timer/alarm expiry
- ✅ Important system alerts
- ✅ New message arrivals
- ✅ Task completions
- ✅ Status changes
Avoid:
- ❌ Every button click
- ❌ Frequent updates (use debouncing)
- ❌ Non-critical information
- ❌ During user speech input
Related
- Vowel Client - Core client API
- Event Notifications Recipe - Advanced patterns
- Speaking State Recipe - Track when AI is speaking
- API Reference - Complete API