diff --git a/components/notification-badge.tsx b/components/notification-badge.tsx
index 2ffb835..969f1fa 100644
--- a/components/notification-badge.tsx
+++ b/components/notification-badge.tsx
@@ -1,6 +1,6 @@
import React, { memo, useState, useEffect } from 'react';
import Link from 'next/link';
-import { Bell, Check, ExternalLink, AlertCircle, LogIn, Kanban, MessageSquare } from 'lucide-react';
+import { Bell, Check, ExternalLink, AlertCircle, LogIn, Kanban, MessageSquare, Mail } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { useNotifications } from '@/hooks/use-notifications';
import { Button } from '@/components/ui/button';
@@ -215,6 +215,12 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
Parole
)}
+ {notification.source === 'email' && (
+
+
+ Courrier
+
+ )}
{formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })}
diff --git a/lib/services/notifications/email-adapter.ts b/lib/services/notifications/email-adapter.ts
index 5397150..9a5548c 100644
--- a/lib/services/notifications/email-adapter.ts
+++ b/lib/services/notifications/email-adapter.ts
@@ -205,10 +205,143 @@ export class EmailAdapter implements NotificationAdapter {
}
async getNotifications(userId: string, page = 1, limit = 20): Promise {
- // For now, return empty array - we can implement this later if needed
- // The notification count is what matters for the badge
- // In the future, we could return unread emails as notifications
- return [];
+ logger.debug('[EMAIL_ADAPTER] getNotifications called', { userId, page, limit });
+
+ try {
+ // Get all accounts from the database
+ const accounts = await prisma.mailCredentials.findMany({
+ where: { userId },
+ select: {
+ id: true,
+ email: true
+ }
+ });
+
+ if (accounts.length === 0) {
+ return [];
+ }
+
+ const notifications: Notification[] = [];
+
+ // For each account, get unread emails from INBOX
+ // Use the same flow as getEmails() but filter for unread only
+ for (const account of accounts) {
+ try {
+ const client = await getImapConnection(userId, account.id);
+
+ // Use the same approach as getEmails() - open mailbox first
+ const mailboxInfo = await client.mailboxOpen('INBOX');
+
+ if (!mailboxInfo || typeof mailboxInfo === 'boolean') {
+ logger.debug('[EMAIL_ADAPTER] Could not open INBOX', {
+ userId,
+ accountId: account.id,
+ });
+ continue;
+ }
+
+ const totalEmails = mailboxInfo.exists || 0;
+
+ if (totalEmails === 0) {
+ continue;
+ }
+
+ // Search for unread emails (same as getNotificationCount logic)
+ const searchResult = await client.search({ seen: false });
+
+ if (!searchResult || searchResult.length === 0) {
+ continue;
+ }
+
+ // Limit the number of results for performance (get more than limit to have enough after filtering)
+ const limitedResults = searchResult.slice(0, limit * 3);
+
+ // Fetch email metadata using the same structure as getEmails()
+ const messages = await client.fetch(limitedResults, {
+ envelope: true,
+ flags: true,
+ uid: true
+ });
+
+ // Convert to notifications (same format as getEmails() processes emails)
+ for await (const message of messages) {
+ // Filter: only process if truly unread (double-check flags)
+ if (message.flags && message.flags.has('\\Seen')) {
+ continue;
+ }
+
+ const envelope = message.envelope;
+ const from = envelope.from?.[0];
+ const subject = envelope.subject || '(Sans objet)';
+ const fromName = from?.name || from?.address || 'Expéditeur inconnu';
+ const fromAddress = from?.address || '';
+
+ const notification: Notification = {
+ id: `email-${account.id}-${message.uid}`,
+ source: 'email',
+ sourceId: message.uid.toString(),
+ type: 'email',
+ title: 'Email',
+ message: `${fromName}${fromAddress ? ` <${fromAddress}>` : ''}: ${subject}`,
+ link: `/courrier?accountId=${account.id}&folder=INBOX&emailId=${message.uid}`,
+ isRead: false,
+ timestamp: envelope.date || new Date(),
+ priority: 'normal',
+ user: {
+ id: fromAddress,
+ name: fromName,
+ },
+ metadata: {
+ accountId: account.id,
+ accountEmail: account.email,
+ folder: 'INBOX',
+ emailId: message.uid.toString(),
+ }
+ };
+
+ notifications.push(notification);
+ }
+
+ // Close mailbox (same as getEmails() does implicitly via connection pool)
+ try {
+ await client.mailboxClose();
+ } catch (closeError) {
+ // Non-fatal, connection pool will handle it
+ logger.debug('[EMAIL_ADAPTER] Error closing mailbox (non-fatal)', {
+ error: closeError instanceof Error ? closeError.message : String(closeError),
+ });
+ }
+ } catch (accountError) {
+ logger.error('[EMAIL_ADAPTER] Error processing account for notifications', {
+ userId,
+ accountId: account.id,
+ error: accountError instanceof Error ? accountError.message : String(accountError),
+ });
+ continue;
+ }
+ }
+
+ // Sort by timestamp (newest first) and apply pagination
+ notifications.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
+
+ const startIndex = (page - 1) * limit;
+ const endIndex = startIndex + limit;
+ const paginatedNotifications = notifications.slice(startIndex, endIndex);
+
+ logger.debug('[EMAIL_ADAPTER] getNotifications result', {
+ total: notifications.length,
+ returned: paginatedNotifications.length,
+ page,
+ limit,
+ });
+
+ return paginatedNotifications;
+ } catch (error) {
+ logger.error('[EMAIL_ADAPTER] Error getting notifications', {
+ error: error instanceof Error ? error.message : String(error),
+ });
+ return [];
+ }
}
async markAsRead(userId: string, notificationId: string): Promise {