Facebook Pixel

Chat Notifications for Offline Users Email and Push Notifications

Learn how to send notifications to offline users when they receive chat messages email notifications, browser push, and managing notification preferences.

Chat Notifications for Offline Users

When a user is offline, they need to know they received a message. This guide covers email and push notifications.

Checking if a User is Online

const onlineUsers = new Map(); const isUserOnline = (userId) => { return onlineUsers.has(userId.toString()); }; // In the message:send handler socket.on("message:send", async ({ targetUserId, text }, callback) => { // Save and broadcast message (as covered in previous guides) const message = await Message.create({ ... }); io.to(roomId).emit("message:received", message); // Check if receiver is online if (!isUserOnline(targetUserId)) { // Receiver is offline send notification await sendOfflineNotification(targetUserId, socket.user, text); } callback({ success: true }); });

Email Notification

const sendOfflineNotification = async (receiverId, sender, text) => { const receiver = await User.findById(receiverId); if (!receiver || receiver.emailBounced || receiver.emailComplained) { return; // Skip if email is invalid or user complained } // Check notification preferences if (receiver.notificationPreferences?.messageEmail === false) { return; // User disabled email notifications } // Rate limit: don't send more than 1 email per 10 minutes const recentNotification = await NotificationLog.findOne({ userId: receiverId, type: "new_message", createdAt: { $gte: new Date(Date.now() - 10 * 60 * 1000) } }); if (recentNotification) { return; // Already notified recently } await sendEmail(receiver.email, "newMessage", { receiverFirstName: receiver.firstName, senderFirstName: sender.firstName, senderLastName: sender.lastName, senderPhotoUrl: sender.photoUrl, messagePreview: text.substring(0, 100), chatUrl: `https://app.yourdomain.com/chat/${sender._id}` }); await NotificationLog.create({ userId: receiverId, type: "new_message", senderId: sender._id, createdAt: new Date() }); };

Email Template

const newMessageTemplate = (data) => ({ subject: `${data.senderFirstName} sent you a message`, html: ` <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;"> <div style="background: #fd267d; padding: 20px; text-align: center;"> <h1 style="color: white; margin: 0;">DevTinder</h1> </div> <div style="padding: 20px;"> <img src="${data.senderPhotoUrl}" style="width: 50px; height: 50px; border-radius: 50%; vertical-align: middle;" /> <h2>${data.senderFirstName} ${data.senderLastName}</h2> <p style="color: #666; padding: 10px; background: #f9f9f9; border-radius: 8px;"> ${data.messagePreview} </p> <a href="${data.chatUrl}" style="display: inline-block; background: #fd267d; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;"> Reply Now </a> </div> </div> `, text: `${data.senderFirstName} sent you: ${data.messagePreview}. Reply at ${data.chatUrl}` });

Browser Push Notifications

// Frontend: Request notification permission const requestNotificationPermission = async () => { if (!("Notification" in window)) return; if (Notification.permission === "default") { const permission = await Notification.requestPermission(); return permission === "granted"; } return Notification.permission === "granted"; }; // Show notification when message received (and tab is not focused) socket.on("message:received", (message) => { if (document.hidden && Notification.permission === "granted") { new Notification(`New message from ${message.sender.firstName}`, { body: message.text, icon: message.sender.photoUrl, badge: "/favicon.ico", tag: "chat-message", data: { url: `/chat/${message.senderId}` } }); } }); // Handle notification click self.addEventListener("notificationclick", (event) => { event.notification.close(); event.waitUntil( clients.openWindow(event.notification.data.url) ); });

Notification Preferences

Add notification preferences to the User model:

const userSchema = new mongoose.Schema({ // ... existing fields ... notificationPreferences: { messageEmail: { type: Boolean, default: true }, messagePush: { type: Boolean, default: true }, connectionEmail: { type: Boolean, default: true }, dailyDigest: { type: Boolean, default: false } } });

Settings API:

router.patch("/notifications/preferences", userAuth, asyncHandler(async (req, res) => { const allowed = ["messageEmail", "messagePush", "connectionEmail", "dailyDigest"]; const updates = {}; for (const key of allowed) { if (req.body[key] !== undefined) { updates[`notificationPreferences.${key}`] = req.body[key]; } } const user = await User.findByIdAndUpdate( req.user._id, { $set: updates }, { new: true } ); res.json({ data: user.notificationPreferences }); }));

The Takeaway

Offline chat notifications involve: checking if the receiver is online (onlineUsers Map), sending an email notification if offline, rate-limiting notifications (1 per 10 minutes), respecting user notification preferences, using browser push notifications for foreground tabs, and logging sent notifications to prevent duplicates. Always provide a settings page for users to control their notification preferences.

Maintain a Map of online users. When a message is sent, check if the receiver is online. If not, send an email notification with the sender's name, message preview, and a link to the chat. Rate-limit to 1 email per 10 minutes and respect user notification preferences.

Request Notification.permission on first visit. When a message is received via Socket.io and the tab is hidden (document.hidden), create a new Notification with the sender's name, message text, and icon. Handle notificationclick to open the chat URL.

Check NotificationLog for a recent notification (within 10 minutes) before sending. If one exists, skip. Also respect user preferences (notificationPreferences.messageEmail). Don't send to bounced or complained emails. Log each notification sent.

Add a notificationPreferences object to the User model (messageEmail, messagePush, connectionEmail, dailyDigest, all booleans). Create a PATCH endpoint to update preferences. Check these flags before sending any notification.

Sender's name and photo, a preview of the message (first 100 characters), a 'Reply Now' button linking to the chat URL, and the app branding. Use inline CSS (email-safe HTML) and include a plain text fallback.

Ready to master Node.js completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.