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