WebRTC Email Integration Guide - nself-org/nchat GitHub Wiki
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: '[email protected]',
userName: 'John Doe',
verificationUrl: 'https://app.com/verify?token=abc123',
verificationCode: '123456',
appName: 'nchat',
expiresInHours: 24,
})
// Password reset
await emailService.sendPasswordReset({
to: '[email protected]',
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: '[email protected]',
userName: 'John Doe',
code: '123456',
appName: 'nchat',
expiresInMinutes: 10,
})
// Welcome email
await emailService.sendWelcomeEmail({
to: '[email protected]',
userName: 'John Doe',
appName: 'nchat',
loginUrl: 'https://app.com/login',
})
// Magic link
await emailService.sendMagicLink({
to: '[email protected]',
userName: 'John Doe',
magicLinkUrl: 'https://app.com/magic?token=abc',
appName: 'nchat',
expiresInMinutes: 15,
})
// New login notification
await emailService.sendNewLoginNotification({
to: '[email protected]',
userName: 'John Doe',
ipAddress: '192.168.1.1',
location: 'San Francisco, CA',
appName: 'nchat',
})
// Password changed
await emailService.sendPasswordChangedNotification({
to: '[email protected]',
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 protected]
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 protected]
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": "[email protected]",
"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: '[email protected]',
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":"[email protected]","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.