diff --git a/public/js/views/emailCompose.js b/public/js/views/emailCompose.js
index 4e4c080..7f11fe9 100644
--- a/public/js/views/emailCompose.js
+++ b/public/js/views/emailCompose.js
@@ -433,10 +433,8 @@ const EmailCompose = (() => {
} else {
const tn = options.ticketTn || '';
const title = options.ticketTitle || '';
- subjectEl.value = tn ? `Re: [Ticket#${tn}] ${title}` : title;
+ subjectEl.value = tn ? `[Ticket#${tn}] Re: ${title}` : title;
}
-
- // Signature and groups select
const sigSelect = document.getElementById('ec-signature-select');
const groupsSelect = document.getElementById('ec-groups-select');
const agentId = App.currentAgentId || 0;
diff --git a/public/js/views/ticketDetail.js b/public/js/views/ticketDetail.js
index b5bdf50..837dbcb 100644
--- a/public/js/views/ticketDetail.js
+++ b/public/js/views/ticketDetail.js
@@ -161,6 +161,7 @@ const TicketDetailView = {
📋
${App.escapeHtml(ticket.title || '(senza titolo)')}
+
${ticket.state_name}
@@ -566,7 +567,7 @@ const TicketDetailView = {
this.updateNoteAttachmentList();
}
- this.bindEvents(ticket, articles, container, groupsData);
+ this.bindEvents(ticket, articles, container, groupsData, attachments);
} catch (err) {
container.innerHTML = `
@@ -580,7 +581,7 @@ const TicketDetailView = {
}
},
- bindEvents(ticket, articles, container, groupsData) {
+ bindEvents(ticket, articles, container, groupsData, attachments) {
// Quick-edit change detection
const fields = document.querySelectorAll('.quick-edit-select:not(#qe-queue-search), #qe-queue, #qe-customer-user-id, #qe-customer-id');
const saveBtn = document.getElementById('qe-save');
diff --git a/routes/email.js b/routes/email.js
index 986ce9f..373fa61 100644
--- a/routes/email.js
+++ b/routes/email.js
@@ -226,7 +226,7 @@ router.post('/send', async (req, res) => {
if (!ticketResult.rows.length) return res.status(404).json({ error: 'Ticket non trovato' });
const { tn, title } = ticketResult.rows[0];
- const subject = customSubject || `Re: [Ticket#${tn}] ${title}`;
+ const subject = customSubject || `[Ticket#${tn}] Re: ${title}`;
// 2. Build BCC list (include OTRS system mailbox if keepHelpdeskCopy is true)
const bccList = [...bcc];
@@ -263,7 +263,8 @@ router.post('/send', async (req, res) => {
const messageId = `<${Date.now()}.${Math.random().toString(36).substring(2)}@pharmaidea.com>`;
// 3. Send via configured mailer (Graph API or SMTP)
- await sendMail({ to, cc, bcc: bccList, subject, bodyHtml: processedBodyHtml, attachments, inlineImages: finalInlineImages, inReplyTo, references, messageId });
+ const mailResult = await sendMail({ to, cc, bcc: bccList, subject, bodyHtml: processedBodyHtml, attachments, inlineImages: finalInlineImages, inReplyTo, references, messageId });
+ const finalMessageId = (mailResult && mailResult.internetMessageId) || messageId;
// 4. Log article in OTRS ticket via DB as a standard Email article
try {
@@ -308,7 +309,7 @@ router.post('/send', async (req, res) => {
await pool.query(`
INSERT INTO article_data_mime (article_id, a_from, a_to, a_cc, a_bcc, a_subject, a_body, a_content_type, a_message_id, incoming_time, create_time, create_by, change_time, change_by)
VALUES ($1, $2, $3, $4, $5, $6, $7, 'text/html; charset=utf-8', $8, $9, $11, $10, $11, $10)`,
- [articleId, aFrom, toList, cc.join(', '), bccList.join(', '), subject, processedBodyHtml, messageId, now, agentId || 1, localNow]
+ [articleId, aFrom, toList, cc.join(', '), bccList.join(', '), subject, processedBodyHtml, finalMessageId, now, agentId || 1, localNow]
);
// Helper to strip HTML tags
diff --git a/server.js b/server.js
index 6ae2e15..14b95be 100644
--- a/server.js
+++ b/server.js
@@ -1,4 +1,4 @@
-require('dotenv').config();
+require('dotenv').config({ path: require('path').resolve(__dirname, '.env') });
const express = require('express');
const cors = require('cors');
const path = require('path');
diff --git a/utils/graphMailer.js b/utils/graphMailer.js
index 81b1038..69cd4f3 100644
--- a/utils/graphMailer.js
+++ b/utils/graphMailer.js
@@ -60,7 +60,7 @@ async function getAccessToken() {
* @param {Array} [options.inlineImages] - [{ cid, content (base64), contentType }]
* @returns {Promise}
*/
-async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [], inReplyTo, references }) {
+async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [], inReplyTo, references, messageId }) {
const sender = process.env.AZURE_MAIL_SENDER;
if (!sender) throw new Error('AZURE_MAIL_SENDER non configurato nel .env');
@@ -115,7 +115,7 @@ async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments
attachments: allAttachments,
internetMessageHeaders: headers.length ? headers : undefined,
},
- saveToSentItems: false,
+ saveToSentItems: true,
};
const url = `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(sender)}/sendMail`;
@@ -130,7 +130,30 @@ async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments
});
if (res.status === 202) {
- return; // Successo (Graph API risponde con 202 No Content)
+ let actualMessageId = messageId;
+ try {
+ // Wait 1.5 seconds for Exchange to process and place it in Sent Items
+ await new Promise(resolve => setTimeout(resolve, 1500));
+
+ const searchUrl = `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(sender)}/mailFolders/sentItems/messages?$filter=subject eq '${subject.replace(/'/g, "''")}'&$top=1&$select=internetMessageId`;
+ const searchRes = await fetch(searchUrl, {
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (searchRes.ok) {
+ const searchData = await searchRes.json();
+ if (searchData.value && searchData.value.length > 0) {
+ actualMessageId = searchData.value[0].internetMessageId;
+ }
+ }
+ } catch (searchErr) {
+ console.warn('[Graph Mailer] Failed to retrieve actual InternetMessageId from Sent Items:', searchErr.message);
+ }
+
+ return { internetMessageId: actualMessageId };
}
const errText = await res.text();
diff --git a/utils/smtpMailer.js b/utils/smtpMailer.js
index 6a78928..a33f148 100644
--- a/utils/smtpMailer.js
+++ b/utils/smtpMailer.js
@@ -29,7 +29,7 @@ function getTransporter() {
* Invia una email tramite SMTP (nodemailer).
* Stessa interfaccia di graphMailer.sendMail.
*/
-async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [], inReplyTo, references }) {
+async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [], inReplyTo, references, messageId }) {
const transporter = getTransporter();
const mailOptions = {
@@ -41,6 +41,7 @@ async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments
html: bodyHtml,
inReplyTo,
references,
+ messageId,
attachments: [
...attachments.map(a => ({
filename: a.filename,
@@ -57,6 +58,7 @@ async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments
};
await transporter.sendMail(mailOptions);
+ return { internetMessageId: messageId };
}
module.exports = { sendMail };