# ๐Ÿ“ง Email WebSocket Integration Guide for React This guide will help you integrate real-time email notifications into your React application using WebSocket connections. ## ๐Ÿš€ Quick Start ### Installation ```bash npm install socket.io-client # or yarn add socket.io-client ``` ### Basic Setup ```typescript import io from "socket.io-client"; const socket = io("ws://localhost:3000/email", { auth: { token: localStorage.getItem("jwt-token"), // Your JWT authentication token }, }); ``` ## ๐Ÿ”Œ Connection Setup ### 1. Basic Connection Hook ```typescript // hooks/useEmailWebSocket.ts import { useEffect, useState } from "react"; import io, { Socket } from "socket.io-client"; interface UseEmailWebSocketOptions { token: string; serverUrl?: string; onConnect?: () => void; onDisconnect?: () => void; onError?: (error: string) => void; } export const useEmailWebSocket = ({ token, serverUrl = "ws://localhost:3000/email", onConnect, onDisconnect, onError }: UseEmailWebSocketOptions) => { const [socket, setSocket] = useState(null); const [isConnected, setIsConnected] = useState(false); useEffect(() => { if (!token) return; const newSocket = io(serverUrl, { auth: { token }, transports: ["websocket", "polling"], }); // Connection events newSocket.on("connect", () => { console.log("โœ… Connected to email WebSocket"); setIsConnected(true); onConnect?.(); }); newSocket.on("disconnect", () => { console.log("โŒ Disconnected from email WebSocket"); setIsConnected(false); onDisconnect?.(); }); newSocket.on("connection_established", (data) => { console.log("๐ŸŽ‰ Email WebSocket connection established:", data); }); newSocket.on("connection_error", (error) => { console.error("๐Ÿ’ฅ Email WebSocket connection error:", error); onError?.(error.error); }); // Health monitoring newSocket.on("ping", () => { newSocket.emit("pong", { timestamp: new Date().toISOString() }); }); setSocket(newSocket); return () => { newSocket.close(); setSocket(null); }; }, [token, serverUrl]); return { socket, isConnected }; }; ``` ### 2. Email Events Hook ```typescript // hooks/useEmailEvents.ts import { useEffect, useState } from "react"; import { Socket } from "socket.io-client"; // TypeScript interfaces (copy from your backend) export interface EmailPayload { messageId: number; userId: string; from: { name?: string; address: string; }; to: Array<{ name?: string; address: string; }>; subject: string; preview?: string; hasAttachments: boolean; timestamp: string; mailboxId: string; mailboxName: string; isRead: boolean; isFlagged: boolean; size: number; } export interface EmailStatusPayload { messageId: number; userId: string; mailboxId: string; timestamp: string; } export interface UnreadCountPayload { userId: string; totalUnread: number; mailboxCounts: Record; timestamp: string; count: number; } interface EmailEventHandlers { onNewEmail?: (email: EmailPayload) => void; onEmailRead?: (data: EmailStatusPayload) => void; onEmailDeleted?: (data: EmailStatusPayload) => void; onEmailSent?: (data: any) => void; onUnreadCountUpdated?: (data: UnreadCountPayload) => void; onMailboxUpdated?: (data: any) => void; } export const useEmailEvents = (socket: Socket | null, handlers: EmailEventHandlers) => { useEffect(() => { if (!socket) return; // Register event handlers if (handlers.onNewEmail) { socket.on("new_email", handlers.onNewEmail); } if (handlers.onEmailRead) { socket.on("email_read", handlers.onEmailRead); } if (handlers.onEmailDeleted) { socket.on("email_deleted", handlers.onEmailDeleted); } if (handlers.onEmailSent) { socket.on("email_sent", handlers.onEmailSent); } if (handlers.onUnreadCountUpdated) { socket.on("unread_count_updated", handlers.onUnreadCountUpdated); } if (handlers.onMailboxUpdated) { socket.on("mailbox_updated", handlers.onMailboxUpdated); } // Cleanup on unmount return () => { socket.off("new_email"); socket.off("email_read"); socket.off("email_deleted"); socket.off("email_sent"); socket.off("unread_count_updated"); socket.off("mailbox_updated"); }; }, [socket, handlers]); }; ``` ## ๐Ÿ“ฑ Complete React Integration ### 1. Email Notification Component ```typescript // components/EmailNotifications.tsx import React, { useState, useCallback } from 'react'; import { useEmailWebSocket, useEmailEvents, EmailPayload, UnreadCountPayload } from '../hooks'; interface EmailNotificationsProps { userToken: string; onEmailClick?: (email: EmailPayload) => void; } export const EmailNotifications: React.FC = ({ userToken, onEmailClick }) => { const [notifications, setNotifications] = useState([]); const [unreadCount, setUnreadCount] = useState(0); const [isVisible, setIsVisible] = useState(false); const { socket, isConnected } = useEmailWebSocket({ token: userToken, onConnect: () => console.log('Email notifications connected'), onError: (error) => console.error('WebSocket error:', error), }); const handleNewEmail = useCallback((email: EmailPayload) => { console.log('๐Ÿ“ง New email received:', email); // Add to notifications list setNotifications(prev => [email, ...prev.slice(0, 9)]); // Keep only 10 latest // Show browser notification if permission granted if (Notification.permission === 'granted') { new Notification(`๐Ÿ“ง New Email: ${email.subject}`, { body: `From: ${email.from.address}\n${email.preview}`, icon: '/email-icon.png', tag: `email-${email.messageId}`, // Prevent duplicates }); } // Play notification sound (optional) const audio = new Audio('/notification.mp3'); audio.play().catch(() => {}); // Ignore if audio fails }, []); const handleUnreadCountUpdate = useCallback((data: UnreadCountPayload) => { setUnreadCount(data.count); }, []); const handleEmailRead = useCallback((data: any) => { // Remove from notifications if it was there setNotifications(prev => prev.filter(notification => notification.messageId !== data.messageId) ); }, []); useEmailEvents(socket, { onNewEmail: handleNewEmail, onUnreadCountUpdated: handleUnreadCountUpdate, onEmailRead: handleEmailRead, }); // Request notification permission on mount React.useEffect(() => { if (Notification.permission === 'default') { Notification.requestPermission(); } }, []); return (
{/* Connection Status */}
{isConnected ? 'Connected' : 'Disconnected'}
{/* Notification Bell */}
setIsVisible(!isVisible)} > ๐Ÿ”” {unreadCount > 0 && ( {unreadCount > 99 ? '99+' : unreadCount} )}
{/* Notifications Dropdown */} {isVisible && (

๐Ÿ“ง Email Notifications

{unreadCount} unread
{notifications.length === 0 ? (
No new emails
) : ( notifications.map((email) => (
onEmailClick?.(email)} >
{email.from.name || email.from.address} {email.hasAttachments && ๐Ÿ“Ž}
{email.subject}
{email.preview}
{new Date(email.timestamp).toLocaleString()}
)) )}
)}
); }; ``` ### 2. Email Dashboard Component ```typescript // components/EmailDashboard.tsx import React, { useState } from 'react'; import { useEmailWebSocket, useEmailEvents } from '../hooks'; interface EmailDashboardProps { userToken: string; } export const EmailDashboard: React.FC = ({ userToken }) => { const [emailStats, setEmailStats] = useState({ totalUnread: 0, newToday: 0, sentToday: 0, }); const { socket, isConnected } = useEmailWebSocket({ token: userToken, }); useEmailEvents(socket, { onNewEmail: (email) => { setEmailStats(prev => ({ ...prev, totalUnread: prev.totalUnread + 1, newToday: prev.newToday + 1, })); }, onEmailSent: () => { setEmailStats(prev => ({ ...prev, sentToday: prev.sentToday + 1, })); }, onUnreadCountUpdated: (data) => { setEmailStats(prev => ({ ...prev, totalUnread: data.count, })); }, }); return (

๐Ÿ“ง Email Dashboard

{emailStats.totalUnread}

Unread Emails

{emailStats.newToday}

Received Today

{emailStats.sentToday}

Sent Today

{isConnected ? '๐ŸŸข' : '๐Ÿ”ด'}

{isConnected ? 'Connected' : 'Disconnected'}

); }; ``` ### 3. Context Provider (Advanced) ```typescript // context/EmailWebSocketContext.tsx import React, { createContext, useContext, ReactNode } from 'react'; import { useEmailWebSocket } from '../hooks/useEmailWebSocket'; import { Socket } from 'socket.io-client'; interface EmailWebSocketContextValue { socket: Socket | null; isConnected: boolean; } const EmailWebSocketContext = createContext(null); interface EmailWebSocketProviderProps { children: ReactNode; token: string; serverUrl?: string; } export const EmailWebSocketProvider: React.FC = ({ children, token, serverUrl, }) => { const { socket, isConnected } = useEmailWebSocket({ token, serverUrl }); return ( {children} ); }; export const useEmailWebSocketContext = () => { const context = useContext(EmailWebSocketContext); if (!context) { throw new Error('useEmailWebSocketContext must be used within EmailWebSocketProvider'); } return context; }; ``` ## ๐ŸŽจ CSS Styles ```css /* styles/email-notifications.css */ .email-notifications { position: relative; display: inline-block; } .connection-status { position: absolute; top: -8px; right: -8px; font-size: 10px; padding: 2px 6px; border-radius: 10px; display: flex; align-items: center; gap: 4px; } .connection-status.connected { background: #d4edda; color: #155724; } .connection-status.disconnected { background: #f8d7da; color: #721c24; } .status-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; } .notification-bell { position: relative; cursor: pointer; font-size: 24px; padding: 8px; border-radius: 50%; transition: background-color 0.2s; } .notification-bell:hover { background-color: #f5f5f5; } .badge { position: absolute; top: 0; right: 0; background: #dc3545; color: white; border-radius: 50%; min-width: 18px; height: 18px; font-size: 11px; display: flex; align-items: center; justify-content: center; font-weight: bold; } .notifications-dropdown { position: absolute; top: 100%; right: 0; width: 350px; max-height: 400px; background: white; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 1000; overflow: hidden; } .notifications-header { padding: 12px 16px; background: #f8f9fa; border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; } .notifications-list { max-height: 300px; overflow-y: auto; } .notification-item { padding: 12px 16px; border-bottom: 1px solid #eee; cursor: pointer; transition: background-color 0.2s; } .notification-item:hover { background-color: #f8f9fa; } .notification-item:last-child { border-bottom: none; } .sender { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; } .attachment-icon { font-size: 12px; } .subject { font-weight: 500; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .preview { color: #666; font-size: 14px; margin-bottom: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .timestamp { font-size: 12px; color: #999; } .no-notifications { padding: 20px; text-align: center; color: #666; } /* Dashboard Styles */ .email-dashboard { padding: 20px; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-top: 20px; } .stat-card { background: white; padding: 20px; border-radius: 8px; border: 1px solid #ddd; text-align: center; } .stat-card h3 { margin: 0 0 8px 0; font-size: 32px; font-weight: bold; } .stat-card h3.connected { color: #28a745; } .stat-card h3.disconnected { color: #dc3545; } .stat-card p { margin: 0; color: #666; font-size: 14px; } ``` ## ๐Ÿงช Testing & Manual Triggers ### Manual Email Check ```typescript // API call to manually trigger email check const triggerEmailCheck = async () => { try { const response = await fetch("/api/email/gateway/check-emails", { method: "POST", headers: { Authorization: `Bearer ${userToken}`, "Content-Type": "application/json", }, }); const result = await response.json(); console.log("โœ… Manual email check triggered:", result); } catch (error) { console.error("โŒ Failed to trigger email check:", error); } }; ``` ### Send Test Notification ```typescript const sendTestNotification = async () => { try { const response = await fetch("/api/email/gateway/test-notification", { method: "POST", headers: { Authorization: `Bearer ${userToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ message: "Test notification from React app!", }), }); const result = await response.json(); console.log("โœ… Test notification sent:", result); } catch (error) { console.error("โŒ Failed to send test notification:", error); } }; ``` ## ๐Ÿ”ง Complete App Integration ```typescript // App.tsx - Complete example import React from 'react'; import { EmailWebSocketProvider } from './context/EmailWebSocketContext'; import { EmailNotifications } from './components/EmailNotifications'; import { EmailDashboard } from './components/EmailDashboard'; function App() { const userToken = localStorage.getItem('jwt-token') || ''; const handleEmailClick = (email: any) => { console.log('Email clicked:', email); // Navigate to email detail page // router.push(`/emails/${email.messageId}`); }; if (!userToken) { return
Please login to access email notifications
; } return (

๐Ÿ“ง Email App

); } export default App; ``` ## ๐Ÿšจ Error Handling & Edge Cases ### Connection Recovery ```typescript const useConnectionRecovery = (socket: Socket | null) => { useEffect(() => { if (!socket) return; const handleReconnect = () => { console.log("๐Ÿ”„ WebSocket reconnecting..."); // Optionally refresh data or show reconnecting state }; const handleReconnectError = () => { console.error("โŒ Failed to reconnect"); // Show user notification or retry logic }; socket.on("reconnect", handleReconnect); socket.on("reconnect_error", handleReconnectError); return () => { socket.off("reconnect", handleReconnect); socket.off("reconnect_error", handleReconnectError); }; }, [socket]); }; ``` ### Token Refresh ```typescript const useTokenRefresh = (socket: Socket | null) => { useEffect(() => { const refreshToken = () => { const newToken = localStorage.getItem("jwt-token"); if (socket && newToken) { socket.auth = { token: newToken }; socket.disconnect(); socket.connect(); } }; // Listen for token refresh events window.addEventListener("token-refreshed", refreshToken); return () => { window.removeEventListener("token-refreshed", refreshToken); }; }, [socket]); }; ``` ## ๐Ÿ“ Environment Configuration ```typescript // config/websocket.ts export const WEBSOCKET_CONFIG = { development: { serverUrl: "ws://localhost:3000/email", reconnectionAttempts: 5, timeout: 20000, }, production: { serverUrl: "wss://your-domain.com/email", reconnectionAttempts: 10, timeout: 30000, }, }; export const getWebSocketConfig = () => { const env = process.env.NODE_ENV || "development"; return WEBSOCKET_CONFIG[env as keyof typeof WEBSOCKET_CONFIG]; }; ``` ## ๐ŸŽฏ Best Practices 1. **๐Ÿ” Security**: Always use HTTPS/WSS in production 2. **๐Ÿ”„ Reconnection**: Implement proper reconnection logic 3. **๐ŸŽฏ Performance**: Limit notification history (max 10-50 items) 4. **๐Ÿ“ฑ UX**: Request notification permissions gracefully 5. **๐Ÿ”Š Audio**: Make notification sounds optional 6. **๐Ÿ’พ Persistence**: Consider storing notifications in localStorage 7. **๐ŸŽจ Theming**: Support light/dark mode for notifications 8. **โ™ฟ Accessibility**: Add proper ARIA labels and keyboard navigation ## ๐Ÿ› Troubleshooting **Connection Issues:** - Check JWT token validity - Verify server URL and port - Check browser console for errors - Ensure CORS is properly configured **Missing Notifications:** - Verify WebSocket event names match backend - Check browser notification permissions - Confirm user is authenticated properly - Test with manual triggers **Performance Issues:** - Limit notification history - Debounce rapid events - Use React.memo for components - Implement virtual scrolling for long lists --- ## ๐Ÿš€ Ready to Go! Your React app is now ready to receive real-time email notifications! The WebSocket connection will automatically: - โœ… Authenticate users with JWT tokens - โœ… Receive new email notifications instantly - โœ… Update unread counts in real-time - โœ… Handle connection recovery automatically - โœ… Show browser notifications (with permission) - โœ… Provide a beautiful UI for email notifications Happy coding! ๐ŸŽ‰