-
Notifications
You must be signed in to change notification settings - Fork 0
WebRTC Email Integration Guide
Complete guide for WebRTC voice/video calling with LiveKit and email notifications integration.
This guide covers the implementation of:
- WebRTC UI Components: CallWindow, StreamPlayer, CallControls, ParticipantGrid
- Email Service: SendGrid/SMTP integration with React Email templates
- LiveKit Integration: Voice and video call infrastructure
Location: src/components/voice-video/CallWindow.tsx
Full-featured video call interface supporting 1-100 participants.
Features:
- β Responsive grid layout (1x1, 2x1, 2x2, 3x3, etc.)
- β Local video preview (mirrored)
- β Remote participant videos
- β Audio/video mute controls
- β Screen sharing
- β Call duration timer
- β Connection quality indicators
- β Speaking indicators (border highlight)
- β Participant list sidebar
- β Minimize/maximize
- β Auto-hide controls
Usage:
import { CallWindow } from '@/components/voice-video/CallWindow'
function MyCallPage() {
const handleEndCall = () => {
// Clean up and navigate away
}
return (
<CallWindow
callId="call-123"
channelName="General Discussion"
participants={participants}
currentUserId="user-123"
onEndCall={handleEndCall}
onToggleMute={() => {}}
onToggleVideo={() => {}}
onToggleScreenShare={() => {}}
/>
)
}Location: src/components/voice-video/StreamPlayer.tsx
Live streaming player with chat and reactions.
Features:
- β HLS/DASH playback
- β Quality selector (Auto, 1080p, 720p, 480p, 360p)
- β Live viewer count
- β Reaction emojis (hearts, likes, smiles)
- β Chat integration
- β Fullscreen mode
- β Auto-hide controls
Usage:
import { StreamPlayer } from '@/components/voice-video/StreamPlayer'
function LiveStreamPage() {
return (
<StreamPlayer
metadata={{
streamId: 'stream-123',
title: 'Product Launch Event',
streamer: { id: 'user-1', name: 'John Doe' },
viewerCount: 1234,
startedAt: new Date(),
isLive: true,
}}
streamUrl="https://stream-url.com/index.m3u8"
messages={chatMessages}
reactions={reactions}
onSendMessage={(msg) => console.log(msg)}
onReaction={(type) => console.log(type)}
/>
)
}Location: src/components/call/call-controls.tsx
Bottom control bar for calls with all call actions.
Features:
- β Mute/unmute audio
- β Enable/disable video
- β Screen sharing toggle
- β Recording (host only)
- β Participant list
- β Settings menu
- β End call button
- β Keyboard shortcuts
Usage:
import { CallControls } from '@/components/call/call-controls'
function MyCallUI() {
return (
<CallControls
isMuted={isMuted}
isVideoEnabled={isVideoEnabled}
isScreenSharing={isScreenSharing}
showVideoControls
showScreenShareControls
callDuration={callDuration}
onToggleMute={() => {}}
onToggleVideo={() => {}}
onToggleScreenShare={() => {}}
onEndCall={() => {}}
onOpenSettings={() => {}}
/>
)
}Location: src/components/voice-video/ParticipantGrid.tsx
Dynamic grid layout with adaptive sizing and spotlight mode.
Features:
- β Auto-adaptive grid (1-100+ participants)
- β Spotlight mode (single participant)
- β Sidebar mode (screen share + thumbnails)
- β Speaking indicators
- β Pin participant
- β Connection quality badges
- β Host controls (mute, remove)
- β Pagination for 100+ participants
Layouts:
-
auto: Automatically choose best layout -
grid: Equal-sized tiles (1x1, 2x1, 2x2, 3x3, 4x4, etc.) -
spotlight: One main participant -
sidebar: Main participant + vertical sidebar
Usage:
import { ParticipantGrid } from '@/components/voice-video/ParticipantGrid'
function CallView() {
return (
<ParticipantGrid
participants={remoteParticipants}
localParticipant={localUser}
layout="auto"
pinnedParticipantId={pinnedId}
isHost={isHost}
onPinParticipant={(id) => setPinnedId(id)}
onRemoveParticipant={(id) => removeFromCall(id)}
onMuteParticipant={(id) => muteParticipant(id)}
/>
)
}Participant Interface:
interface CallParticipant {
id: string
name: string
avatarUrl?: string
isMuted: boolean
isVideoOff: boolean
isScreenSharing: boolean
isSpeaking: boolean
connectionQuality?: number // 0-100
stream?: MediaStream
isHost?: boolean
}Location: src/lib/email/email.service.ts
Multi-provider email service with automatic fallback.
Providers:
-
SendGrid (Production) -
SENDGRID_API_KEY -
SMTP (Development) - Mailpit on
localhost:1025 - Console (Fallback) - Logs to console
Provider Selection:
// Automatic provider selection
if (SENDGRID_API_KEY) β SendGrid
else if (SMTP_HOST) β SMTP
else if (development) β Console
else β Console with warningAll templates use React Email for beautiful, responsive emails.
Location: src/emails/templates/
Available Templates:
-
email-verification.tsx- Verify email address -
password-reset.tsx- Reset password link -
welcome.tsx- Welcome new users -
new-login.tsx- New device/location login alert -
password-changed.tsx- Password change confirmation -
mention-notification.tsx- User mentioned in channel -
dm-notification.tsx- New direct message -
digest.tsx- Daily/weekly activity summary
import { emailService } from '@/lib/email/email.service'
// Email verification
await emailService.sendEmailVerification({
to: 'user@example.com',
userName: 'John Doe',
verificationUrl: 'https://app.com/verify?token=abc123',
verificationCode: '123456',
appName: 'nchat',
expiresInHours: 24,
})
// Password reset
await emailService.sendPasswordReset({
to: 'user@example.com',
userName: 'John Doe',
resetUrl: 'https://app.com/reset?token=xyz789',
appName: 'nchat',
expiresInMinutes: 60,
ipAddress: '192.168.1.1',
userAgent: 'Chrome 120',
})
// 2FA code
await emailService.send2FACode({
to: 'user@example.com',
userName: 'John Doe',
code: '123456',
appName: 'nchat',
expiresInMinutes: 10,
})
// Welcome email
await emailService.sendWelcomeEmail({
to: 'user@example.com',
userName: 'John Doe',
appName: 'nchat',
loginUrl: 'https://app.com/login',
})
// Magic link
await emailService.sendMagicLink({
to: 'user@example.com',
userName: 'John Doe',
magicLinkUrl: 'https://app.com/magic?token=abc',
appName: 'nchat',
expiresInMinutes: 15,
})
// New login notification
await emailService.sendNewLoginNotification({
to: 'user@example.com',
userName: 'John Doe',
ipAddress: '192.168.1.1',
location: 'San Francisco, CA',
appName: 'nchat',
})
// Password changed
await emailService.sendPasswordChangedNotification({
to: 'user@example.com',
userName: 'John Doe',
ipAddress: '192.168.1.1',
appName: 'nchat',
})Email service is already integrated in these routes:
-
/api/auth/signup- Sends welcome + verification emails -
/api/auth/password-reset- Sends reset link -
/api/auth/verify-email- Sends verification email -
/api/auth/resend-verification- Resends verification -
/api/auth/change-password- Sends password changed notification
# Generate LiveKit API key and secret
LIVEKIT_API_KEY=$(openssl rand -hex 16)
LIVEKIT_API_SECRET=$(openssl rand -hex 32)
echo "LIVEKIT_API_KEY=$LIVEKIT_API_KEY"
echo "LIVEKIT_API_SECRET=$LIVEKIT_API_SECRET"Add to .env.local:
# LiveKit Server
NEXT_PUBLIC_LIVEKIT_URL=ws://localhost:7880
# API Credentials
LIVEKIT_API_KEY=your_api_key_here
LIVEKIT_API_SECRET=your_api_secret_hereUsing Docker Compose:
# Start LiveKit
docker-compose -f docker-compose.livekit.yml up -d
# Check status
docker-compose -f docker-compose.livekit.yml ps
# View logs
docker-compose -f docker-compose.livekit.yml logs -f livekit
# Stop
docker-compose -f docker-compose.livekit.yml downVerify Installation:
# Check LiveKit API
curl http://localhost:7880/
# Should return LiveKit server infodocker-compose.livekit.yml:
- LiveKit server container
- Port mappings (7880, 7881, 7882, 50000-60000)
- Volume mount for config
- Health checks
livekit.yaml:
- Server configuration
- Room settings (max participants, timeouts)
- WebRTC port ranges
- Audio/video codecs
- TURN server settings
- Logging configuration
For production, update livekit.yaml:
# Use external IP
rtc:
use_external_ip: true
external_ip: 'your-server-public-ip'
# Disable dev mode
dev_mode: false
# Set log level
logging:
level: warn
# Configure proper TURN server
turn:
enabled: true
domain: 'turn.yourdomain.com'
external_tls: trueMailpit is included in the nself backend stack for local email testing.
Access:
- SMTP:
localhost:1025 - Web UI: http://localhost:8025
Configuration (.env.local):
# Email provider (SMTP for dev)
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false
SMTP_USER=
SMTP_PASSWORD=
# Sender info
EMAIL_FROM=noreply@nchat.app
EMAIL_FROM_NAME=nChatTesting:
- Start backend:
cd .backend && nself start - Access Mailpit: http://localhost:8025
- Trigger email (signup, password reset, etc.)
- Check Mailpit UI for sent emails
1. Get SendGrid API Key:
- Sign up at https://sendgrid.com
- Create API key with "Mail Send" permission
- Verify sender email/domain
2. Configure Environment (.env.local):
# SendGrid
SENDGRID_API_KEY=SG.your_api_key_here
# Sender info
EMAIL_FROM=noreply@yourdomain.com
EMAIL_FROM_NAME=Your App Name3. Verify Setup:
# Test email sending via API
curl -X POST http://localhost:3000/api/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "Test123!@#",
"username": "testuser"
}'Branding:
Edit email templates in src/emails/templates/:
// Common branding
const branding = {
appName: 'Your App',
logoUrl: 'https://yourdomain.com/logo.png',
supportEmail: 'support@yourdomain.com',
primaryColor: '#6366f1',
}Template Structure:
import EmailLayout from '../components/EmailLayout'
import EmailHeading from '../components/EmailHeading'
import EmailButton from '../components/EmailButton'
export default function MyEmail({ userName, actionUrl }) {
return (
<EmailLayout preview="Email preview text">
<EmailHeading>Hello {userName}!</EmailHeading>
<Text>Your email content here...</Text>
<EmailButton href={actionUrl}>Take Action</EmailButton>
</EmailLayout>
)
}import { useState } from 'react'
import { CallWindow } from '@/components/voice-video/CallWindow'
import { ParticipantGrid } from '@/components/voice-video/ParticipantGrid'
import { useCallStore } from '@/stores/call-store'
function VideoCallPage() {
const { currentCall, participants, localParticipant } = useCallStore()
const [pinnedId, setPinnedId] = useState<string | null>(null)
if (!currentCall) return <div>No active call</div>
return (
<div className="h-screen">
<ParticipantGrid
participants={participants}
localParticipant={localParticipant}
layout="auto"
pinnedParticipantId={pinnedId}
isHost={currentCall.hostId === localParticipant.id}
onPinParticipant={setPinnedId}
/>
</div>
)
}// src/app/api/calls/invite/route.ts
import { emailService } from '@/lib/email/email.service'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const { userId, callId, inviterName } = await request.json()
// Get user email
const user = await getUserById(userId)
// Send invitation email
await emailService.send({
to: user.email,
subject: `${inviterName} invited you to a call`,
html: `
<h1>Call Invitation</h1>
<p>${inviterName} invited you to join a call.</p>
<a href="${process.env.NEXT_PUBLIC_APP_URL}/call/${callId}">
Join Call
</a>
`,
})
return NextResponse.json({ success: true })
}import { StreamPlayer } from '@/components/voice-video/StreamPlayer'
import { useWebSocket } from '@/hooks/use-websocket'
function LiveStreamPage({ streamId }: { streamId: string }) {
const { messages, sendMessage, reactions, sendReaction } = useWebSocket(streamId)
return (
<StreamPlayer
metadata={{
streamId,
title: 'Live Event',
streamer: { id: 'user-1', name: 'Host Name' },
viewerCount: reactions.length,
startedAt: new Date(),
isLive: true,
}}
streamUrl={`https://stream.example.com/${streamId}/index.m3u8`}
messages={messages}
reactions={reactions}
onSendMessage={sendMessage}
onReaction={sendReaction}
/>
)
}Issue: No video/audio
- Check browser permissions (camera/microphone)
- Verify LiveKit server is running
- Check WebRTC ports are open (50000-60000)
- Test with
chrome://webrtc-internals/
Issue: Poor call quality
- Check network bandwidth
- Reduce max resolution in
livekit.yaml - Enable TURN server for NAT traversal
- Check
connectionQualitymetric
Issue: Connection failed
- Verify
NEXT_PUBLIC_LIVEKIT_URLis correct - Check LiveKit API credentials
- Ensure network can reach LiveKit server
- Check firewall rules
Issue: Emails not sending
- Check email provider configuration
- Verify API keys are valid
- Check logs:
src/lib/email/email.service.ts - Test with Mailpit in development
Issue: Emails going to spam
- Configure SPF, DKIM, DMARC records
- Use verified sender domain
- Avoid spam trigger words
- Maintain low bounce rate
Issue: SendGrid errors
- Check API key permissions
- Verify sender is verified
- Check SendGrid dashboard for blocks
- Review rate limits
- Adaptive Bitrate:
# livekit.yaml
limits:
track_bitrate: 2500000 # 2.5 Mbps- Participant Pagination:
// ParticipantGrid automatically paginates at 16 participants
const PARTICIPANTS_PER_PAGE = 16- Video Resolution:
// Request lower resolution for thumbnails
const constraints = {
video: { width: 320, height: 240 },
}- Queue System: Use background jobs for bulk emails
- Rate Limiting: Respect provider limits
- Batching: Group notifications into digest emails
- Caching: Cache rendered templates
- Token-Based Auth: Generate LiveKit tokens server-side
- Room Access Control: Verify permissions before joining
- Recording Consent: Notify participants of recording
- E2EE: Enable end-to-end encryption for sensitive calls
- Rate Limiting: Prevent email spam
- Verification: Verify email addresses
- Unsubscribe: Include unsubscribe links
- Content: Sanitize user-generated content in emails
// src/lib/livekit/token-generator.ts
import { AccessToken } from 'livekit-server-sdk'
export function generateToken(roomName: string, participantName: string, userId: string): string {
const token = new AccessToken(process.env.LIVEKIT_API_KEY!, process.env.LIVEKIT_API_SECRET!, {
identity: userId,
name: participantName,
})
token.addGrant({
roomJoin: true,
room: roomName,
canPublish: true,
canSubscribe: true,
})
return token.toJwt()
}-
Test WebRTC Components:
pnpm dev # Navigate to /demo/video-call -
Test Email Service:
# Start Mailpit cd .backend && nself start # Open Mailpit UI open http://localhost:8025 # Trigger test email curl -X POST http://localhost:3000/api/auth/signup -d '{"email":"test@example.com","password":"Test123!"}'
-
Deploy LiveKit (Production):
- Use managed service: https://cloud.livekit.io
- Or self-host with Kubernetes
- Configure SSL/TLS
- Set up monitoring
-
Configure SendGrid (Production):
- Verify domain
- Configure DNS records (SPF, DKIM, DMARC)
- Set up webhooks
- Monitor deliverability
-
Documentation:
/docs -
Examples:
/examples - LiveKit Docs: https://docs.livekit.io
- SendGrid Docs: https://docs.sendgrid.com
- React Email: https://react.email
Same as nself-chat project.
nself-chat v0.3.0 | GitHub | Issues | Discussions | Demo
Edit this page | MIT License | Β© 2026
(See π Security section below for 2FA, PIN Lock, and security audits.)
(Search lives in π Reference below.)
- π¬ Advanced Messaging
- π E2EE Setup
- π Search Setup
- π Call Management
- πΊ Live Streaming
- π₯οΈ Screen Sharing
- πΉ Video Calling
- ποΈ Voice Calling
- π± Mobile Optimization
- π§ͺ Testing
- π i18n
- π API Overview
- π Complete Reference
- π» API Examples
- π€ Bot API
- π Auth API
- π GraphQL Schema
- π Deployment Overview
- π³ Docker
- βΈοΈ Kubernetes
- β Helm Charts
- β Production Checklist
- π Production Validation
- π’ Multi-Tenant
- ποΈ Architecture
- π Diagrams
- ποΈ Database Schema
- π Project Structure
- π TypeScript Types
- π SPORT Reference
- π 2FA
- π¬ Messaging
- π Call Management
- π Call State Machine
- π E2EE
- πΊ Live Streaming
- π± Mobile Calls
- π PIN Lock
- π Polls
- π₯οΈ Screen Sharing
- π Search
- π Social Media
- ποΈ Voice Calling
- π Security Overview
- π‘οΈ Security Audit
- β‘ Performance
- π Best Practices
- π 2FA
- π PIN Lock
- π E2EE
- π‘οΈ E2EE Audit
v1.0.0 β’ 2026