feat: funzione per l'invio della posta elettronica da otrs-turbo. fix: corretta consuntivazione automatica. feat: aggiunto link apri in otrs anche nel numero del ticket in cima
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* utils/graphMailer.js
|
||||
* Invia email tramite Microsoft Graph API (per Exchange con 2FA/OAuth2).
|
||||
* Gestisce automaticamente il token OAuth2 con cache e refresh.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
let cachedToken = null;
|
||||
let tokenExpiresAt = 0;
|
||||
|
||||
/**
|
||||
* Ottiene un access token OAuth2 da Microsoft (client credentials flow).
|
||||
* Il token viene cachato per circa 55 minuti per evitare richieste continue.
|
||||
*/
|
||||
async function getAccessToken() {
|
||||
const now = Date.now();
|
||||
if (cachedToken && now < tokenExpiresAt) {
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
const { AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET } = process.env;
|
||||
|
||||
const url = `https://login.microsoftonline.com/${AZURE_TENANT_ID}/oauth2/v2.0/token`;
|
||||
|
||||
const body = new URLSearchParams({
|
||||
client_id: AZURE_CLIENT_ID,
|
||||
client_secret: AZURE_CLIENT_SECRET,
|
||||
scope: 'https://graph.microsoft.com/.default',
|
||||
grant_type: 'client_credentials',
|
||||
});
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: body.toString(),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
throw new Error(`[Graph Auth] Errore ottenendo token OAuth2: ${res.status} ${errText}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
cachedToken = data.access_token;
|
||||
// Scade in data.expires_in secondi, refresh 5 minuti prima
|
||||
tokenExpiresAt = now + (data.expires_in - 300) * 1000;
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invia una email tramite Microsoft Graph API.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string[]} options.to - Destinatari (array di email)
|
||||
* @param {string[]} [options.cc] - CC (array di email)
|
||||
* @param {string[]} [options.bcc] - BCC (array di email)
|
||||
* @param {string} options.subject - Oggetto email
|
||||
* @param {string} options.bodyHtml - Corpo HTML
|
||||
* @param {Array} [options.attachments] - [{ filename, content (base64), contentType }]
|
||||
* @param {Array} [options.inlineImages] - [{ cid, content (base64), contentType }]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [] }) {
|
||||
const sender = process.env.AZURE_MAIL_SENDER;
|
||||
if (!sender) throw new Error('AZURE_MAIL_SENDER non configurato nel .env');
|
||||
|
||||
const token = await getAccessToken();
|
||||
|
||||
const toRecipients = to.map(addr => ({
|
||||
emailAddress: { address: addr }
|
||||
}));
|
||||
const ccRecipients = cc.map(addr => ({
|
||||
emailAddress: { address: addr }
|
||||
}));
|
||||
const bccRecipients = bcc.map(addr => ({
|
||||
emailAddress: { address: addr }
|
||||
}));
|
||||
|
||||
// Costruisce gli allegati (file + immagini inline)
|
||||
const allAttachments = [
|
||||
...attachments.map(a => ({
|
||||
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||
name: a.filename,
|
||||
contentType: a.contentType || 'application/octet-stream',
|
||||
contentBytes: a.content, // già base64
|
||||
})),
|
||||
...inlineImages.map(img => ({
|
||||
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||
name: img.cid,
|
||||
contentId: img.cid,
|
||||
contentType: img.contentType || 'image/png',
|
||||
contentBytes: img.content, // già base64
|
||||
isInline: true,
|
||||
})),
|
||||
];
|
||||
|
||||
const payload = {
|
||||
message: {
|
||||
subject,
|
||||
body: {
|
||||
contentType: 'HTML',
|
||||
content: bodyHtml,
|
||||
},
|
||||
toRecipients,
|
||||
ccRecipients,
|
||||
bccRecipients,
|
||||
attachments: allAttachments,
|
||||
},
|
||||
saveToSentItems: false,
|
||||
};
|
||||
|
||||
const url = `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(sender)}/sendMail`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (res.status === 202) {
|
||||
return; // Successo (Graph API risponde con 202 No Content)
|
||||
}
|
||||
|
||||
const errText = await res.text();
|
||||
throw new Error(`[Graph Mail] Errore invio email: ${res.status} ${errText}`);
|
||||
}
|
||||
|
||||
module.exports = { sendMail };
|
||||
Reference in New Issue
Block a user