Add optional mailbox (UPN/SMTP) routing on email/folder/thread tools via
users/{upn}/... and X-AnchorMailbox. New list-mailboxes probes primary,
OUTLOOK_SHARED_MAILBOXES seeds, cache, and candidates. Send supports
mailbox-rooted sendMail and onBehalfOf. MSAL requests Mail.*.Shared;
check-auth-status reports token scp gaps. Docs, env example, tests.
169 lines
4.3 KiB
JavaScript
169 lines
4.3 KiB
JavaScript
/**
|
|
* Send email functionality
|
|
*/
|
|
const config = require('../config');
|
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
const { ensureAuthenticated } = require('../auth');
|
|
const { normalizeMailbox, buildPath, withMailboxHeaders } = require('../utils/mailbox');
|
|
|
|
/**
|
|
* Send email handler
|
|
* @param {object} args - Tool arguments
|
|
* @returns {object} - MCP response
|
|
*/
|
|
async function handleSendEmail(args) {
|
|
const {
|
|
to,
|
|
cc,
|
|
bcc,
|
|
subject,
|
|
body,
|
|
importance = 'normal',
|
|
saveToSentItems = true,
|
|
from: fromArg,
|
|
onBehalfOf = false
|
|
} = args;
|
|
|
|
const mb = normalizeMailbox(args.mailbox);
|
|
const onBehalf = onBehalfOf === true || onBehalfOf === 'true';
|
|
|
|
if (!to) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: "Recipient (to) is required."
|
|
}]
|
|
};
|
|
}
|
|
|
|
if (!subject) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: "Subject is required."
|
|
}]
|
|
};
|
|
}
|
|
|
|
if (!body) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: "Body content is required."
|
|
}]
|
|
};
|
|
}
|
|
|
|
try {
|
|
const accessToken = await ensureAuthenticated();
|
|
|
|
const toRecipients = to.split(',').map(email => {
|
|
email = email.trim();
|
|
return {
|
|
emailAddress: {
|
|
address: email
|
|
}
|
|
};
|
|
});
|
|
|
|
const ccRecipients = cc ? cc.split(',').map(email => {
|
|
email = email.trim();
|
|
return {
|
|
emailAddress: {
|
|
address: email
|
|
}
|
|
};
|
|
}) : [];
|
|
|
|
const bccRecipients = bcc ? bcc.split(',').map(email => {
|
|
email = email.trim();
|
|
return {
|
|
emailAddress: {
|
|
address: email
|
|
}
|
|
};
|
|
}) : [];
|
|
|
|
const fromAddr = (fromArg && String(fromArg).trim())
|
|
|| (mb.kind === 'user' ? mb.smtpOrUpn : null);
|
|
|
|
// Default: when mailbox is a shared UPN, send via users/{upn}/sendMail (Send As style).
|
|
// onBehalfOf=true forces me/sendMail with from=shared (Send on Behalf style).
|
|
let sendPath = 'me/sendMail';
|
|
let anchorMb = mb;
|
|
if (mb.kind === 'user' && !onBehalf) {
|
|
sendPath = buildPath(mb, 'sendMail');
|
|
} else if (fromAddr && onBehalf) {
|
|
sendPath = 'me/sendMail';
|
|
anchorMb = normalizeMailbox(fromAddr);
|
|
} else if (mb.kind === 'user') {
|
|
sendPath = buildPath(mb, 'sendMail');
|
|
}
|
|
|
|
const message = {
|
|
subject,
|
|
body: {
|
|
// New Outlook + Graph: prefer HTML when the body looks like a document.
|
|
// Match <html, <!doctype html, or a leading HTML fragment with common tags.
|
|
contentType: /<!DOCTYPE\s+html|<html[\s>]|<(?:table|div|h[1-6]|p)\b/i.test(body || '')
|
|
? 'HTML'
|
|
: 'Text',
|
|
content: body
|
|
},
|
|
toRecipients,
|
|
ccRecipients: ccRecipients.length > 0 ? ccRecipients : undefined,
|
|
bccRecipients: bccRecipients.length > 0 ? bccRecipients : undefined,
|
|
importance
|
|
};
|
|
|
|
if (fromAddr) {
|
|
message.from = {
|
|
emailAddress: {
|
|
address: fromAddr
|
|
}
|
|
};
|
|
}
|
|
|
|
const emailObject = {
|
|
message,
|
|
saveToSentItems
|
|
};
|
|
|
|
await callGraphAPI(
|
|
accessToken,
|
|
'POST',
|
|
sendPath,
|
|
emailObject,
|
|
null,
|
|
{ headers: withMailboxHeaders(anchorMb) }
|
|
);
|
|
|
|
const fromNote = fromAddr ? `\nFrom: ${fromAddr}${onBehalf ? ' (on behalf)' : ''}` : '';
|
|
const mailboxNote = mb.kind === 'user' ? `\nMailbox: ${mb.smtpOrUpn}` : '';
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Email sent successfully!${mailboxNote}${fromNote}\n\nSubject: ${subject}\nRecipients: ${toRecipients.length}${ccRecipients.length > 0 ? ` + ${ccRecipients.length} CC` : ''}${bccRecipients.length > 0 ? ` + ${bccRecipients.length} BCC` : ''}\nMessage Length: ${body.length} characters`
|
|
}]
|
|
};
|
|
} catch (error) {
|
|
if (error.message === 'Authentication required') {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
}]
|
|
};
|
|
}
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Error sending email: ${error.message}`
|
|
}]
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = handleSendEmail;
|