2 * @file emailAutomation.js
3 * @module routes/emailAutomation
4 * @description Email-to-ticket automation endpoints with Microsoft Graph OAuth integration.
5 * Handles inbound email processing, tenant email settings, OAuth flow for Office 365,
6 * and outbound email sending via Graph API, SMTP, or Mailgun.
8 * @requires pushEmailToRedis
10 * @requires @azure/msal-node
11 * @requires services/db
12 * @requires services/email
13 * @author RMM-PSA Development Team
14 * @copyright 2026 RMM-PSA Platform
15 * @license Proprietary
19 * @apiDefine EmailAutomation Email Automation
20 * Email-to-ticket automation and Microsoft Graph integration
23const express = require('express');
24const router = express.Router();
25const { pushEmail } = require('../pushEmailToRedis');
26const { v4: uuidv4 } = require('uuid');
27const msal = require('@azure/msal-node');
30 * @api {post} /api/email/inbound Process inbound email
31 * @apiName ProcessInboundEmail
32 * @apiGroup EmailAutomation
33 * @apiDescription Receive inbound emails and queue for AI parsing to create tickets.
34 * Generates unique email ID, pushes to Redis queue for async processing by AI worker.
35 * No authentication required (called by email webhook/IMAP forwarder).
36 * @apiParam {string} from Sender email address
37 * @apiParam {string} [to] Recipient email address
38 * @apiParam {string} subject Email subject
39 * @apiParam {string} [text] Plain text email body
40 * @apiParam {string} [html] HTML email body (used if text not provided)
41 * @apiSuccess {string} status="queued" Email queued for processing
42 * @apiSuccess {string} emailId Generated UUID for email tracking
43 * @apiError (400) {String} error="Missing required fields" From, subject, or body missing
44 * @apiExample {curl} Example:
45 * curl -X POST http://localhost:3000/api/email/inbound \\
46 * -H "Content-Type: application/json" \\
48 * "from": "customer@example.com",
49 * "to": "support@msp.com",
50 * "subject": "Printer not working",
51 * "text": "My printer has stopped working since this morning."
53 * @apiSuccessExample {json} Success-Response:
57 * "emailId": "550e8400-e29b-41d4-a716-446655440000"
60router.post('/inbound', async (req, res) => {
61 const { from, to, subject, text, html } = req.body;
62 if (!from || !subject || !(text || html)) {
63 return res.status(400).json({ error: 'Missing required fields' });
65 // Generate a unique email ID
66 const emailId = uuidv4();
67 // Push to Redis for AI parsing
68 pushEmail({ id: emailId, from, to, subject, text: text || html });
69 res.json({ status: 'queued', emailId });
73 * @api {get} /api/email/settings/:tenant_id Get email settings
74 * @apiName GetEmailSettings
75 * @apiGroup EmailAutomation
76 * @apiDescription Retrieve tenant email automation settings from database.
77 * Returns email configuration (provider, credentials, OAuth tokens).
78 * @apiParam {number} tenant_id Tenant ID
79 * @apiSuccess {string} [provider] Email provider ("smtp", "mailgun", "office365")
80 * @apiSuccess {string} [username] Email username/address
81 * @apiSuccess {object} [graph] Microsoft Graph OAuth tokens (if using Office 365)
82 * @apiSuccess {object} [smtp] SMTP server configuration
83 * @apiSuccess {object} [mailgun] Mailgun API configuration
84 * @apiError (404) {String} error="Tenant not found" Tenant ID not in database
85 * @apiError (500) {String} error="Server error" Database query failed
86 * @apiExample {curl} Example:
87 * curl -X GET http://localhost:3000/api/email/settings/2
88 * @apiSuccessExample {json} Success-Response:
91 * "provider": "office365",
92 * "username": "support@acme.com",
94 * "accessToken": "eyJ0eXAiOi...",
95 * "refreshToken": "M.R3_BAY..."
99router.get('/settings/:tenant_id', async (req, res) => {
100 const { tenant_id } = req.params;
102 const pool = require('../services/db');
103 const result = await pool.query('SELECT email_automation FROM tenants WHERE tenant_id = $1', [tenant_id]);
104 if (!result.rows.length) return res.status(404).json({ error: 'Tenant not found' });
105 res.json(result.rows[0].email_automation || {});
107 res.status(500).json({ error: 'Server error' });
112 * @api {post} /api/email/settings/:tenant_id Save email settings
113 * @apiName SaveEmailSettings
114 * @apiGroup EmailAutomation
115 * @apiDescription Update tenant email automation settings in database.
116 * Accepts full settings object (provider, credentials, tokens).
117 * @apiParam {number} tenant_id Tenant ID
118 * @apiParam {string} [provider] Email provider ("smtp", "mailgun", "office365")
119 * @apiParam {string} [username] Email username/address
120 * @apiParam {object} [smtp] SMTP server configuration
121 * @apiParam {string} [smtp.host] SMTP server hostname
122 * @apiParam {number} [smtp.port] SMTP port
123 * @apiParam {string} [smtp.password] SMTP password
124 * @apiParam {object} [mailgun] Mailgun API configuration
125 * @apiParam {string} [mailgun.apiKey] Mailgun API key
126 * @apiParam {string} [mailgun.domain] Mailgun domain
127 * @apiSuccess {boolean} success=true Settings saved successfully
128 * @apiError (500) {String} error="Server error" Database update failed
129 * @apiExample {curl} Example:
130 * curl -X POST http://localhost:3000/api/email/settings/2 \\
131 * -H "Content-Type: application/json" \\
133 * "provider": "smtp",
134 * "username": "support@acme.com",
136 * "host": "mail.acme.com",
138 * "password": "secret123"
141 * @apiSuccessExample {json} Success-Response:
147router.post('/settings/:tenant_id', async (req, res) => {
148 const { tenant_id } = req.params;
149 const settings = req.body;
151 const pool = require('../services/db');
152 await pool.query('UPDATE tenants SET email_automation = $1 WHERE tenant_id = $2', [JSON.stringify(settings), tenant_id]);
153 res.json({ success: true });
155 res.status(500).json({ error: 'Server error' });
160 * @api {post} /api/email/oauth/:tenant_id Trigger OAuth flow (stub)
161 * @apiName TriggerEmailOAuth
162 * @apiGroup EmailAutomation
163 * @apiDescription Stub endpoint for triggering Microsoft Graph OAuth flow.
164 * Currently not implemented - returns placeholder response.
165 * Use GET /oauth/:tenant_id/start for actual OAuth initiation.
166 * @apiParam {number} tenant_id Tenant ID
167 * @apiSuccess {string} status="not_implemented"
168 * @apiSuccess {string} message Placeholder message
169 * @apiExample {curl} Example:
170 * curl -X POST http://localhost:3000/api/email/oauth/2
171 * @apiSuccessExample {json} Success-Response:
174 * "status": "not_implemented",
175 * "message": "OAuth flow not yet implemented."
178router.post('/oauth/:tenant_id', async (req, res) => {
179 // TODO: Implement Microsoft Graph OAuth flow and store tokens in email_automation
180 res.json({ status: 'not_implemented', message: 'OAuth flow not yet implemented.' });
184 * @api {get} /api/email/oauth/:tenant_id/start Start OAuth flow
185 * @apiName StartEmailOAuth
186 * @apiGroup EmailAutomation
187 * @apiDescription Initiate Microsoft Graph OAuth2 flow for Office 365 email integration.
188 * Generates authorization URL using MSAL. User redirects to Microsoft login.
189 * Requires msalConfig with clientId, authority configured.
190 * @apiParam {number} tenant_id Tenant ID (used in OAuth state parameter)
191 * @apiSuccess {string} url Microsoft authorization URL for user redirect
192 * @apiError (500) {String} error="Failed to generate auth URL" MSAL configuration error
193 * @apiExample {curl} Example:
194 * curl -X GET http://localhost:3000/api/email/oauth/2/start
195 * @apiSuccessExample {json} Success-Response:
198 * "url": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=..."
201router.get('/oauth/:tenant_id/start', async (req, res) => {
202 const { tenant_id } = req.params;
203 const msalClient = new msal.ConfidentialClientApplication(msalConfig);
204 const authCodeUrlParameters = {
205 scopes: ['offline_access', 'Mail.Read', 'Mail.Send'],
209 const url = await msalClient.getAuthCodeUrl(authCodeUrlParameters);
212 res.status(500).json({ error: 'Failed to generate auth URL' });
217 * @api {get} /api/email/oauth/callback OAuth callback
218 * @apiName EmailOAuthCallback
219 * @apiGroup EmailAutomation
220 * @apiDescription Handle Microsoft Graph OAuth callback after user authorization.
221 * Exchanges authorization code for access/refresh tokens using MSAL.
222 * Stores tokens in tenant email_automation settings.
223 * @apiParam {string} code Authorization code (query parameter from Microsoft)
224 * @apiParam {string} state Tenant ID passed as state parameter (query parameter)
225 * @apiSuccess {boolean} success=true Tokens acquired and saved successfully
226 * @apiError (400) {String} error="Missing code or tenant_id" Required parameters not provided
227 * @apiError (500) {String} error="OAuth callback failed" Token exchange or database save failed
228 * @apiExample {curl} Example:
229 * curl -X GET "http://localhost:3000/api/email/oauth/callback?code=M.R3_BAY...&state=2"
230 * @apiSuccessExample {json} Success-Response:
236router.get('/oauth/callback', async (req, res) => {
237 const code = req.query.code;
238 const tenant_id = req.query.state; // Pass tenant_id in state param for security
239 if (!code || !tenant_id) return res.status(400).json({ error: 'Missing code or tenant_id' });
240 const msalClient = new msal.ConfidentialClientApplication(msalConfig);
242 const tokenResponse = await msalClient.acquireTokenByCode({
244 scopes: ['offline_access', 'Mail.Read', 'Mail.Send'],
247 // Save tokens to tenant email_automation config
249 "UPDATE tenants SET email_automation = jsonb_set(coalesce(email_automation, '{}'), '{graph}', $1::jsonb) WHERE tenant_id = $2",
250 [JSON.stringify(tokenResponse), tenant_id]
252 res.json({ success: true });
254 res.status(500).json({ error: 'OAuth callback failed', details: err.message });
259 * @api {post} /api/email/test/:tenant_id Test email connection
260 * @apiName TestEmailConnection
261 * @apiGroup EmailAutomation
262 * @apiDescription Test tenant email connection settings (stub implementation).
263 * Verifies email configuration exists. TODO: Implement actual connection test (IMAP/Graph API).
264 * @apiParam {number} tenant_id Tenant ID
265 * @apiSuccess {boolean} success=true Settings found (not actual connection test)
266 * @apiSuccess {string} message Stub message
267 * @apiError (404) {String} error="Tenant not found" Tenant ID not in database
268 * @apiError (400) {String} error="No email settings found" Email automation not configured
269 * @apiError (500) {String} error="Server error" Database query failed
270 * @apiExample {curl} Example:
271 * curl -X POST http://localhost:3000/api/email/test/2
272 * @apiSuccessExample {json} Success-Response:
276 * "message": "Connection test stub (implement real test)"
279router.post('/test/:tenant_id', async (req, res) => {
280 const { tenant_id } = req.params;
282 const pool = require('../services/db');
283 const result = await pool.query('SELECT email_automation FROM tenants WHERE tenant_id = $1', [tenant_id]);
284 if (!result.rows.length) return res.status(404).json({ error: 'Tenant not found' });
285 const settings = result.rows[0].email_automation;
286 // TODO: Actually test connection (IMAP/Graph API)
287 if (!settings || !settings.username) return res.status(400).json({ error: 'No email settings found' });
288 res.json({ success: true, message: 'Connection test stub (implement real test)' });
290 res.status(500).json({ error: 'Server error' });
295 * @api {post} /api/email/send/:tenant_id Send email
297 * @apiGroup EmailAutomation
298 * @apiDescription Send email via Microsoft Graph, SMTP, or Mailgun.
299 * Uses tenant email automation settings to determine provider and credentials.
300 * Supports Office 365 Graph API, generic SMTP, or Mailgun.
301 * @apiParam {number} tenant_id Tenant ID
302 * @apiParam {string} to Recipient email address
303 * @apiParam {string} subject Email subject
304 * @apiParam {string} [body] Email body (HTML or text, deprecated - use html/text)
305 * @apiParam {string} [html] HTML email body
306 * @apiParam {string} [text] Plain text email body
307 * @apiParam {string} [provider] Override provider ("office365", "smtp", "mailgun")
308 * @apiSuccess {boolean} success=true Email sent successfully
309 * @apiSuccess {Object} [result] Send result details (SMTP/Mailgun only)
310 * @apiError (400) {String} error="Missing to, subject, or body/html/text" Required parameters missing
311 * @apiError (404) {String} error="Tenant not found" Tenant ID not in database
312 * @apiError (400) {String} error="No Graph access token found" Office 365 tokens not configured
313 * @apiError (500) {String} error="Failed to send email" Send operation failed
314 * @apiExample {curl} Example (Office 365):
315 * curl -X POST http://localhost:3000/api/email/send/2 \\
316 * -H "Content-Type: application/json" \\
318 * "to": "customer@example.com",
319 * "subject": "Ticket Update",
320 * "html": "<p>Your ticket has been resolved.</p>",
321 * "provider": "office365"
323 * @apiSuccessExample {json} Success-Response:
329router.post('/send/:tenant_id', async (req, res) => {
330 const { tenant_id } = req.params;
331 const { to, subject, body, html, text, provider } = req.body;
332 if (!to || !subject || !(body || html || text)) return res.status(400).json({ error: 'Missing to, subject, or body/html/text' });
334 const pool = require('../services/db');
335 const { sendEmail } = require('../services/email');
336 // Get tenant email settings
337 const result = await pool.query('SELECT email_automation FROM tenants WHERE tenant_id = $1', [tenant_id]);
338 if (!result.rows.length) return res.status(404).json({ error: 'Tenant not found' });
339 const settings = result.rows[0].email_automation || {};
340 // Determine provider: 'mailgun', 'smtp', or 'office365' (graph)
341 let emailProvider = provider || settings.provider || 'smtp';
342 // If office365/graph, use old logic
343 if (emailProvider === 'office365' || emailProvider === 'graph') {
344 const graph = settings.graph;
345 if (!graph || !graph.accessToken) return res.status(400).json({ error: 'No Graph access token found' });
346 const { Client } = require('@microsoft/microsoft-graph-client');
347 require('isomorphic-fetch');
348 // TODO: Refresh token if expired
349 const client = Client.init({
350 authProvider: (done) => { done(null, graph.accessToken); }
352 await client.api('/me/sendMail').post({
355 body: { contentType: 'HTML', content: body || html },
356 toRecipients: [{ emailAddress: { address: to } }]
358 saveToSentItems: true
360 return res.json({ success: true });
362 // Otherwise, use sendEmail (Mailgun/SMTP)
363 const emailResult = await sendEmail({
368 provider: emailProvider,
371 res.json({ success: true, result: emailResult });
373 res.status(500).json({ error: 'Failed to send email', details: err.message });
377// This route can be called by your email parser (IMAP/SMTP webhook)
378// The AI worker will process and create a ticket if needed
379module.exports = router;