Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
5.2 KiB
Email Roundtrip Implementation Plan (Holistic)
Date: November 18, 2025 Status: Planned
Objective
Complete the email roundtrip feature by implementing outbound notification logic that ensures channel consistency and correct threading. When a ticket is created from an inbound email, any subsequent agent replies must be sent:
- From the same email address that received the original email.
- Via the same provider (Microsoft/Google) to ensure deliverability and header integrity.
- With correct In-Reply-To and References headers to maintain the conversation thread in the customer's email client.
Architecture Analysis
Current State
- Inbound:
system-email-processing-workflowcreates tickets and storesemail_metadata(includingproviderId,messageId,references). - Inbound Adapters:
MicrosoftGraphAdapterandGmailAdaptercurrently only support reading emails. - Outbound:
TenantEmailServiceusesEmailProviderManagerto send emails, typically defaulting to a single configured outbound provider (SMTP/Resend). - Subscriber:
ticketEmailSubscriberlistens for events but doesn't currently support channel-specific routing or threading headers.
The Gap
The current system treats inbound and outbound email as separate pipelines. There is no mechanism to "reply via the inbound channel." If support@acme.com (Microsoft) receives an email, the system currently replies via noreply@platform.com (Resend), breaking the "From" address and potentially the thread.
Implementation Plan
1. Enhance Inbound Adapters (Bi-directional Capability)
Modify the existing inbound adapters to support sending emails. This allows us to reuse the existing authentication and connection logic.
- File:
server/src/services/email/providers/MicrosoftGraphAdapter.ts- Implement
sendEmail(message: EmailMessage): Promise<EmailSendResult>. - Use the Graph API
/me/sendMailor/users/{id}/sendMail. - Map
In-Reply-ToandReferencesfrommessage.headersto the API payload.
- Implement
- File:
server/src/services/email/providers/GmailAdapter.ts- Implement
sendEmail(message: EmailMessage): Promise<EmailSendResult>. - Use the Gmail API
users.messages.send. - Construct a raw MIME message that includes the threading headers.
- Implement
2. Upgrade EmailProviderManager (Channel Routing)
Enable the manager to route emails through specific providers based on ID, not just tenant defaults.
- File:
server/src/services/email/EmailProviderManager.ts- Add method
sendEmailViaProvider(providerId: string, message: EmailMessage, tenantId: string). - Logic:
- Retrieve the provider configuration from the database using
providerId. - Instantiate the appropriate adapter (
MicrosoftGraphAdapterorGmailAdapter). - Call
adapter.sendEmail(message).
- Retrieve the provider configuration from the database using
- Add method
3. Update Service Layer (Context Propagation)
Pass the routing and threading information from the business logic down to the provider manager.
- File:
server/src/types/email.types.ts&server/src/lib/email/BaseEmailService.ts- Update
EmailMessageandBaseEmailParamsto include:headers?: Record<string, string>(for threading).providerId?: string(for routing).
- Update
- File:
server/src/lib/services/TenantEmailService.ts- Update
sendEmailto handleproviderId. - If
providerIdis present, delegate toproviderManager.sendEmailViaProvider. - Otherwise, fall back to the default
providerManager.sendEmail.
- Update
- File:
server/src/lib/notifications/sendEventEmail.ts- Update
SendEmailParamsandsendEventEmailto accept and passproviderIdandheaders.
- Update
4. Wire Up the Subscriber (The Glue)
Connect the event system to the new logic.
- File:
server/src/lib/eventBus/subscribers/ticketEmailSubscriber.ts- In
handleTicketCommentAdded:- Filter: Ensure comment is public and from an agent/system.
- Context: Retrieve
ticket.email_metadata. - Routing: Extract
providerIdfromemail_metadata. - Threading: Construct
In-Reply-To(frommessageId) andReferences. - Send: Call
sendNotificationIfEnabledpassingproviderIdandheaders.
- In
UX/Configuration Considerations
- Implicit Configuration: By reusing the inbound provider for replies, we avoid complex "mapping" UI. If a user connects
support@acme.comfor inbound, replies automatically go out viasupport@acme.com. - Setup Screens: No immediate changes required to Setup UI, as the "connection" already exists. Future enhancements could allow configuring a specific "Signature" or "Display Name" for the inbound channel.
Verification Strategy
- Unit Tests: Test
sendEmailin adapters with mocks. - Integration: Simulate a comment on a ticket created via email. Verify
EmailProviderManagerloads the specific provider and sends the email. - Roundtrip E2E:
- Send email to
inbound@test.com-> Ticket Created. - Agent replies -> Email sent via
inbound@test.com(verified via logs/headers). - Customer replies -> Thread continues correctly.
- Send email to