Skip to content

Event Notifications ​

Learn how to programmatically trigger AI voice responses for application events.

Overview ​

Event notifications allow you to make the AI speak about things happening in your app without requiring user voice input. This is perfect for:

  • Order confirmations
  • Timer expiry
  • New messages
  • System alerts
  • Status updates

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()
});

Common Patterns ​

Order Confirmation ​

typescript
async function handleOrderPlaced(order: Order) {
  // Process the order
  const result = await processOrder(order);
  
  // Notify user via voice
  await vowel.notifyEvent('Your order has been placed successfully!', {
    orderId: result.id,
    total: result.total,
    estimatedDelivery: result.estimatedDelivery
  });
}

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);
      this.timerId = null;
    }
  }
}

// Usage
const timer = new VoiceTimer();
timer.start(5); // 5-minute timer

New Message Notification ​

typescript
// Listen for new messages
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) {
  // Add to cart
  cart.add(product, quantity);
  
  // Notify user
  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
    });
  }
}

Advanced Patterns ​

Debounced Notifications ​

Prevent notification spam by debouncing:

typescript
import { debounce } from 'lodash';

const notifyCartUpdate = debounce(async (cart: Cart) => {
  await vowel.notifyEvent('Cart updated', {
    itemCount: cart.items.length,
    total: cart.getTotal()
  });
}, 1000);

// Usage
function updateCart(product: Product) {
  cart.add(product);
  notifyCartUpdate(cart); // Will only notify after 1 second of inactivity
}

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';

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>;
}

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 ​

  1. Keep messages concise - Voice notifications should be brief and clear
  2. Provide context - Include relevant data in the context object
  3. Don't spam - Use debouncing for frequent events
  4. Check connection - Verify session is active before notifying
  5. Handle errors - Always have a fallback for failed notifications
  6. Respect preferences - Allow users to disable voice notifications