src — email
src — email
The src/email module provides a comprehensive, unified interface for email integration, encompassing IMAP (Internet Message Access Protocol) for receiving, SMTP (Simple Mail Transfer Protocol) for sending, and webhook functionality for event notifications. It is designed to abstract away the complexities of interacting with different email protocols and services, offering a consistent API for developers.
Important Note on Implementation: The ImapClient and SmtpClient within this module are mock implementations. They simulate email server behavior for development and testing purposes without requiring actual external email services or libraries. In a production environment, these clients would be replaced or integrated with robust third-party libraries like nodemailer for SMTP and node-imap for IMAP.
Module Architecture
The module is structured into three main parts:
- Types (
src/email/types.ts): Defines all data structures and configurations used across the email system. - Clients (
src/email/client.ts): Provides low-level, protocol-specific (IMAP/SMTP) client functionalities. These are the mock implementations. - Service (
src/email/service.ts): Offers a high-level, unified API that orchestrates the IMAP and SMTP clients, manages webhooks, and handles polling/syncing. It follows a singleton pattern for easy access throughout the application.
graph TD
subgraph Email Module
A[EmailService] --> B(ImapClient)
A --> C(SmtpClient)
A --> D(WebhookManager)
B -- Emits Events --> A
C -- Emits Events --> A
D -- Emits Events --> A
A -- Uses --> E(EmailServiceConfig)
B -- Uses --> F(ImapConfig)
C -- Uses --> G(SmtpConfig)
D -- Uses --> H(EmailWebhookConfig)
A -- Emits Events --> App(Application)
end
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#bbf,stroke:#333,stroke-width:2px
style C fill:#bbf,stroke:#333,stroke-width:2px
style D fill:#bfb,stroke:#333,stroke-width:2px
style App fill:#eee,stroke:#333,stroke-width:2px
Core Concepts and Types (src/email/types.ts)
This file defines the foundational interfaces and types that govern data exchange and configuration within the email module.
Key Types
EmailAddress: Represents an email recipient or sender, e.g.,{ name?: string; address: string; }.EmailMessage: A comprehensive interface for an email, includingfrom,to,subject,date,text,html,attachments,flags,messageId, etc.EmailFlag: A union type for standard IMAP message flags like'seen','answered','flagged','deleted','draft','recent'.EmailFolder: Describes an email folder with properties likename,path,delimiter,specialUse,totalMessages,unseenMessages.ImapConfig: Configuration for the IMAP client, includinghost,port,secure,user,password,oauth2.SmtpConfig: Configuration for the SMTP client, similar toImapConfigbut with SMTP-specific options likepool.ImapSearchCriteria: Defines the criteria for searching IMAP messages (e.g.,seen,unseen,from,subject,before,uid).SendMailOptions: Options for sending an email via SMTP, includingfrom,to,subject,text,html,attachments.SendMailResult: The result object returned after sending an email, containingmessageId,accepted,rejectedrecipients.EmailWebhookConfig: Configuration for a webhook, specifyingurl,secret,eventsto subscribe to,retries, andtimeout.EmailWebhookEvent: A union type for events that can trigger webhooks, such as'message.received','message.sent','folder.created'.EmailWebhookPayload: The structure of the data sent in a webhook request.EmailServiceConfig: The top-level configuration for theEmailService, allowing setup of IMAP, SMTP, Gmail (future), and webhooks.EmailServiceStats: Provides runtime statistics for theEmailService.
Default Configurations
The module exports DEFAULT_IMAP_CONFIG and DEFAULT_SMTP_CONFIG to provide sensible defaults for common settings like ports and security.
Client Implementations (src/email/client.ts)
This file contains the mock implementations of the IMAP and SMTP clients, along with general email utility functions.
Utility Functions
parseEmailAddress(input: string | EmailAddress): EmailAddress: Parses a string (e.g., "Name") or an EmailAddressobject into a standardizedEmailAddressobject.formatEmailAddress(addr: EmailAddress): string: Formats anEmailAddressobject back into a string (e.g., "Name"). generateMessageId(domain = 'codebuddy.local'): string: Generates a uniqueMessage-IDheader value, useful for tracking emails.
ImapClient (Mock Implementation)
The ImapClient class simulates an IMAP client, allowing operations like connecting, listing folders, selecting folders, searching, fetching messages, managing flags, moving/copying/deleting messages, and entering IDLE mode.
- Extends
EventEmitter: Emits events forconnected,disconnected,error,mail(new mail),expunge(message deleted), andflags(flags changed). - Constructor:
new ImapClient(config: ImapConfig)initializes the client with IMAP configuration. - Mock Data: Internally uses
Mapobjects (mockFolders,mockMessages) to store simulated email folders and messages. - Key Methods:
connect(): Simulates connecting to an IMAP server.disconnect(): Simulates disconnecting.listFolders(): Returns a list of mockEmailFolderobjects.selectFolder(path: string): Sets the currently selected folder.search(criteria: ImapSearchCriteria): Filters mock messages based on criteria and returns UIDs.fetch(uids: number | number[]): Retrieves mockEmailMessageobjects by UID.addFlags(uids: number | number[], flags: EmailFlag | EmailFlag[]): Adds flags to mock messages.move(uids: number | number[], destFolder: string): Moves mock messages between folders.delete(uids: number | number[], permanent = false): Marks messages as deleted or moves them to 'Trash'.expunge(): Permanently removes messages marked as 'deleted' from the current folder.idle(): Simulates IMAP IDLE mode, resolving after a timeout.append(folder: string, message: Partial: Adds a new mock message to a specified folder.) addMockMessage(folder: string, message: Partial: A helper for testing to easily inject messages.) - Internal Logic:
matchesCriteriahandles the search logic, andupdateFolderCountskeeps folder statistics current.ensureConnectedandensureFolderSelectedenforce state.
SmtpClient (Mock Implementation)
The SmtpClient class simulates an SMTP client for sending emails.
- Extends
EventEmitter: Emits events forconnected,disconnected,error, andsent. - Constructor:
new SmtpClient(config: SmtpConfig)initializes the client with SMTP configuration. - Key Methods:
connect(): Simulates connecting to an SMTP server.disconnect(): Simulates disconnecting.send(options: SendMailOptions): Simulates sending an email, returning aSendMailResult. Stores sent messages insentMessagesfor testing.verify(): Simulates verifying the connection.getSentMessages(): Returns a list ofSendMailResultfor testing.clearSentMessages(): Clears the list of sent messages for testing.
Service Layer (src/email/service.ts)
This file provides the high-level EmailService which acts as the primary interface for interacting with email functionalities. It integrates the IMAP and SMTP clients and adds webhook management and polling capabilities.
WebhookManager
Manages the registration, removal, and triggering of webhooks.
- Extends
EventEmitter: Emitswebhook-sentandwebhook-failedevents. - Constructor:
new WebhookManager(webhooks: EmailWebhookConfig[] = [])initializes with an optional list of webhooks. - Key Methods:
addWebhook(webhook: EmailWebhookConfig): Adds a new webhook configuration.removeWebhook(url: string): Removes a webhook by its URL.getWebhooks(): Returns the list of configured webhooks.trigger(event: EmailWebhookEvent, data: EmailWebhookPayload['data']): Sends webhook requests to all registered webhooks that subscribe to the givenevent. Includes retry logic and signature generation usingcrypto.sendWebhook(private): Handles the actual (simulated) HTTP request for a webhook.
EmailService
The central component of the email module, orchestrating all functionalities.
- Extends
EventEmitter: Emitsconnected,disconnected,error,message(new message received),sync(folder synced),idle(IMAP IDLE cycle completed), and forwardswebhook-sent/webhook-failedevents fromWebhookManager. - Constructor:
new EmailService(config: EmailServiceConfig)initializes the service with the overall configuration. It creates instances ofImapClient,SmtpClient, andWebhookManagerbased on the provided config. - State Management: Tracks connection status, polling intervals, and service statistics.
- Key Methods:
- Lifecycle:
connect(): Initializes and connects to IMAP and SMTP clients. Starts polling ifpollIntervalis configured.disconnect(): Disconnects clients and stops polling.isConnected(): Checks the connection status of configured clients.- IMAP Operations: Delegates to
ImapClient. Many methods accept an optionalfolderargument, callingimapClient.selectFolder()internally before performing the operation. listFolders(),selectFolder(path),search(criteria, folder?),fetchMessages(uids, folder?),fetchMessage(uid, folder?).markAsRead(uids, folder?),markAsUnread(uids, folder?),flagMessages(uids, flags, folder?): Modify message flags. Triggers webhooks formessage.readandmessage.flagged.moveMessages(uids, destFolder, srcFolder?),deleteMessages(uids, folder?, permanent?): Manipulate messages. Triggersmessage.deletedwebhook.createFolder(path),deleteFolder(path): Manage folders. Triggersfolder.createdandfolder.deletedwebhooks.- SMTP Operations: Delegates to
SmtpClient. sendEmail(options): Sends an email. Triggersmessage.sentwebhook.replyToEmail(originalMessage, options): Constructs and sends a reply.forwardEmail(originalMessage, options): Constructs and sends a forwarded email.- Webhook Management: Delegates to
WebhookManager. addWebhook(webhook),removeWebhook(url),getWebhooks().- Sync & Polling:
syncFolder(folder = 'INBOX'): Fetches unseen messages from a folder, emitsmessageevents, and triggersmessage.receivedwebhooks. UpdateslastSyncstat.startPolling(interval?),stopPolling(): Manages periodic calls tosyncFolder.startIdle(): Enters IMAP IDLE mode, continuously listening for new mail and syncing.- Statistics:
getStats(): ReturnsEmailServiceStats.resetStats(): Resets internal statistics.- Testing Helpers:
addMockMessage(folder, message): Delegates toImapClientfor injecting mock messages.getImapClient(),getSmtpClient(): Provides access to the underlying client instances for advanced testing.- Private Methods:
ensureImapConnected,ensureSmtpConnectedenforce connection state.setupImapListenersandsetupSmtpListenersforward client events to the service.
Singleton Access
The EmailService is designed to be accessed as a singleton:
getEmailService(config?: EmailServiceConfig): EmailService:- Returns the single instance of
EmailService. - If no instance exists and
configis provided, it initializes the service. - Throws an error if called without
configwhen no instance is present. resetEmailService(): void:- Disconnects the current service instance (if any) and clears the singleton, allowing for re-initialization, particularly useful in testing environments.
Usage Patterns
Initialization and Connection
import { getEmailService, EmailServiceConfig } from 39;./email/index.js39;;
const config: EmailServiceConfig = {
imap: {
host: 39;mock.imap.com39;,
port: 993,
secure: true,
user: 39;test@example.com39;,
password: 39;password39;,
},
smtp: {
host: 39;mock.smtp.com39;,
port: 587,
secure: false,
user: 39;test@example.com39;,
password: 39;password39;,
},
webhooks: [
{
url: 39;https:class="hl-cmt">//my-app.com/email-events39;,
events: [39;message.received39;, 39;message.sent39;],
secret: 39;super-secret-key39;,
},
],
pollInterval: 30000, class="hl-cmt">// Poll every 30 seconds
};
const emailService = getEmailService(config);
async function startEmailService() {
try {
await emailService.connect();
console.log(39;Email service connected.39;);
emailService.on(39;message39;, (message) => {
console.log(`New message received: ${message.subject} from ${message.from[0].address}`);
});
emailService.on(39;error39;, (error) => {
console.error(39;Email service error:39;, error);
});
emailService.on(39;webhook-sent39;, (url, payload) => {
console.log(`Webhook sent to ${url} for event ${payload.event}`);
});
} catch (error) {
console.error(39;Failed to connect email service:39;, error);
}
}
startEmailService();
Sending an Email
import { getEmailService } from 39;./email/index.js39;;
const emailService = getEmailService(); class="hl-cmt">// Get the existing instance
async function sendTestEmail() {
try {
const result = await emailService.sendEmail({
from: { name: 39;My App39;, address: 39;test@example.com39; },
to: 39;recipient@example.com39;,
subject: 39;Hello from Email Service!39;,
text: 39;This is a test email sent via the Email Service.39;,
html: 39;<b>This is a test email</b> sent via the <i>Email Service</i>.39;,
});
console.log(39;Email sent successfully:39;, result.messageId);
} catch (error) {
console.error(39;Failed to send email:39;, error);
}
}
sendTestEmail();
Fetching Messages
import { getEmailService } from 39;./email/index.js39;;
const emailService = getEmailService();
async function fetchInboxMessages() {
try {
await emailService.selectFolder(39;INBOX39;);
const uids = await emailService.search({ unseen: true });
console.log(`Found ${uids.length} unseen messages.`);
if (uids.length > 0) {
const messages = await emailService.fetchMessages(uids);
for (const message of messages) {
console.log(`- Subject: ${message.subject}, From: ${message.from[0].address}`);
await emailService.markAsRead(message.uid!, 39;INBOX39;);
}
}
} catch (error) {
console.error(39;Failed to fetch messages:39;, error);
}
}
fetchInboxMessages();
Event Handling
The EmailService (and its underlying clients) are EventEmitter instances, allowing you to subscribe to various events:
EmailServiceEvents:connected: Fired when all configured clients are successfully connected.disconnected: Fired when all clients are disconnected.error(error: Error): Fired when an error occurs in any client or service operation.message(message: EmailMessage): Fired when a new message is received (e.g., duringsyncFolderoridle).sync(folder: string, count: number): Fired after a folder synchronization completes, indicating the number of new messages found.idle: Fired when an IMAP IDLE cycle completes.webhook-sent(url: string, payload: EmailWebhookPayload): Fired when a webhook request is successfully sent.webhook-failed(url: string, payload: EmailWebhookPayload, error: Error): Fired when a webhook request fails after retries.
ImapClientEvents:connected,disconnected,error(error: Error)mail(numNew: number): Indicates new mail in the selected folder.expunge(uid: number): A message withuidhas been permanently removed.flags(uid: number, flags: EmailFlag[]): Flags for a message have changed.
SmtpClientEvents:connected,disconnected,error(error: Error)sent(result: SendMailResult): An email has been sent.
Extensibility and Real-World Integration
As noted, the ImapClient and SmtpClient are mock implementations. To transition to a production environment:
- Replace Client Logic: The
ImapClientandSmtpClientclasses would need to be re-implemented to use actual email client libraries (e.g.,node-imapfor IMAP,nodemailerfor SMTP). - Maintain Interface: Crucially, the public API (
connect,send,fetch,search, etc.) and the emitted events of these client classes should remain consistent to avoid breaking theEmailServicelayer. - Configuration: The
ImapConfigandSmtpConfigtypes are designed to be compatible with common configurations for these libraries, making the transition smoother. - Gmail Integration: The
GmailConfigand related types are placeholders for potential future integration with the Gmail API, which offers a different set of capabilities beyond standard IMAP/SMTP.
This modular design allows the core EmailService and WebhookManager logic to remain stable while the underlying protocol implementations can be swapped out or enhanced.