@marlinjai/email-mcp 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -3
- package/dist/index.js +369 -103
- package/dist/index.js.map +4 -4
- package/dist/setup/wizard.js +239 -102
- package/dist/setup/wizard.js.map +3 -3
- package/package.json +3 -1
package/dist/setup/wizard.js
CHANGED
|
@@ -892,6 +892,53 @@ function buildGmailQuery(query) {
|
|
|
892
892
|
return parts.join(" ");
|
|
893
893
|
}
|
|
894
894
|
|
|
895
|
+
// src/providers/mime.ts
|
|
896
|
+
import MailComposer from "nodemailer/lib/mail-composer/index.js";
|
|
897
|
+
import { convert } from "html-to-text";
|
|
898
|
+
function htmlToText(html) {
|
|
899
|
+
return convert(html, {
|
|
900
|
+
wordwrap: false,
|
|
901
|
+
selectors: [
|
|
902
|
+
{ selector: "img", format: "skip" },
|
|
903
|
+
{ selector: "a", options: { hideLinkHrefIfSameAsText: true } }
|
|
904
|
+
]
|
|
905
|
+
}).trim();
|
|
906
|
+
}
|
|
907
|
+
function normalizeBody(body) {
|
|
908
|
+
const html = body.html ? body.html : void 0;
|
|
909
|
+
if (body.text) return { text: body.text, html };
|
|
910
|
+
if (html) return { text: htmlToText(html), html };
|
|
911
|
+
return { text: "" };
|
|
912
|
+
}
|
|
913
|
+
function toAddress(contact) {
|
|
914
|
+
return contact.name ? { name: contact.name, address: contact.email } : contact.email;
|
|
915
|
+
}
|
|
916
|
+
function toAddressList(contacts) {
|
|
917
|
+
return contacts?.length ? contacts.map(toAddress) : void 0;
|
|
918
|
+
}
|
|
919
|
+
async function buildMimeMessage(from, params) {
|
|
920
|
+
const { text, html } = normalizeBody(params.body);
|
|
921
|
+
const mail = {
|
|
922
|
+
from,
|
|
923
|
+
to: toAddressList(params.to),
|
|
924
|
+
cc: toAddressList(params.cc),
|
|
925
|
+
bcc: toAddressList(params.bcc),
|
|
926
|
+
subject: params.subject,
|
|
927
|
+
text,
|
|
928
|
+
html,
|
|
929
|
+
inReplyTo: params.inReplyTo,
|
|
930
|
+
references: params.references?.length ? params.references : void 0,
|
|
931
|
+
attachments: params.attachments?.map((a) => ({
|
|
932
|
+
filename: a.filename,
|
|
933
|
+
content: a.content,
|
|
934
|
+
contentType: a.contentType
|
|
935
|
+
}))
|
|
936
|
+
};
|
|
937
|
+
const node = new MailComposer(mail).compile();
|
|
938
|
+
node.keepBcc = true;
|
|
939
|
+
return node.build();
|
|
940
|
+
}
|
|
941
|
+
|
|
895
942
|
// src/providers/gmail/adapter.ts
|
|
896
943
|
var GmailAdapter = class {
|
|
897
944
|
providerType = ProviderType.Gmail;
|
|
@@ -1034,7 +1081,7 @@ var GmailAdapter = class {
|
|
|
1034
1081
|
}
|
|
1035
1082
|
async sendEmail(params) {
|
|
1036
1083
|
const gmail = this.ensureConnected();
|
|
1037
|
-
const raw = this.
|
|
1084
|
+
const raw = await this.buildRaw(params);
|
|
1038
1085
|
const res = await gmail.users.messages.send({
|
|
1039
1086
|
userId: "me",
|
|
1040
1087
|
requestBody: { raw }
|
|
@@ -1046,7 +1093,7 @@ var GmailAdapter = class {
|
|
|
1046
1093
|
}
|
|
1047
1094
|
async createDraft(params) {
|
|
1048
1095
|
const gmail = this.ensureConnected();
|
|
1049
|
-
const raw = this.
|
|
1096
|
+
const raw = await this.buildRaw(params);
|
|
1050
1097
|
const res = await gmail.users.drafts.create({
|
|
1051
1098
|
userId: "me",
|
|
1052
1099
|
requestBody: {
|
|
@@ -1057,7 +1104,7 @@ var GmailAdapter = class {
|
|
|
1057
1104
|
}
|
|
1058
1105
|
async updateDraft(draftId, params) {
|
|
1059
1106
|
const gmail = this.ensureConnected();
|
|
1060
|
-
const raw = this.
|
|
1107
|
+
const raw = await this.buildRaw(params);
|
|
1061
1108
|
const res = await gmail.users.drafts.update({
|
|
1062
1109
|
userId: "me",
|
|
1063
1110
|
id: draftId,
|
|
@@ -1330,23 +1377,36 @@ var GmailAdapter = class {
|
|
|
1330
1377
|
requestBody: { addLabelIds: ["SPAM"], removeLabelIds: ["INBOX"] }
|
|
1331
1378
|
});
|
|
1332
1379
|
}
|
|
1333
|
-
|
|
1334
|
-
const gmail = this.ensureConnected();
|
|
1380
|
+
filterCriteria(matchType, value) {
|
|
1335
1381
|
const criteria = {};
|
|
1336
|
-
switch (
|
|
1382
|
+
switch (matchType) {
|
|
1337
1383
|
case "senderDomain":
|
|
1338
|
-
criteria.from = `@${
|
|
1384
|
+
criteria.from = `@${value}`;
|
|
1339
1385
|
break;
|
|
1340
1386
|
case "senderAddress":
|
|
1341
|
-
criteria.from =
|
|
1387
|
+
criteria.from = value;
|
|
1342
1388
|
break;
|
|
1343
1389
|
case "subjectContains":
|
|
1344
|
-
criteria.subject =
|
|
1390
|
+
criteria.subject = value;
|
|
1345
1391
|
break;
|
|
1346
1392
|
case "headerContains":
|
|
1347
|
-
criteria.query =
|
|
1393
|
+
criteria.query = value;
|
|
1348
1394
|
break;
|
|
1349
1395
|
}
|
|
1396
|
+
return criteria;
|
|
1397
|
+
}
|
|
1398
|
+
/** The reverse of filterCriteria, for listing. A best-effort reading of filters made elsewhere. */
|
|
1399
|
+
readCriteria(criteria) {
|
|
1400
|
+
if (typeof criteria?.from === "string" && criteria.from.startsWith("@")) {
|
|
1401
|
+
return { matchType: "senderDomain", value: criteria.from.slice(1) };
|
|
1402
|
+
}
|
|
1403
|
+
if (criteria?.from) return { matchType: "senderAddress", value: criteria.from };
|
|
1404
|
+
if (criteria?.subject) return { matchType: "subjectContains", value: criteria.subject };
|
|
1405
|
+
return { matchType: "headerContains", value: criteria?.query || "" };
|
|
1406
|
+
}
|
|
1407
|
+
async createBlockRule(rule) {
|
|
1408
|
+
const gmail = this.ensureConnected();
|
|
1409
|
+
const criteria = this.filterCriteria(rule.matchType, rule.value);
|
|
1350
1410
|
const action = rule.action === "delete" ? { addLabelIds: ["TRASH"] } : { removeLabelIds: ["INBOX"] };
|
|
1351
1411
|
const res = await gmail.users.settings.filters.create({
|
|
1352
1412
|
userId: "me",
|
|
@@ -1357,25 +1417,11 @@ var GmailAdapter = class {
|
|
|
1357
1417
|
async listBlockRules() {
|
|
1358
1418
|
const gmail = this.ensureConnected();
|
|
1359
1419
|
const res = await gmail.users.settings.filters.list({ userId: "me" });
|
|
1360
|
-
return (res.data.filter || []).map((f) => {
|
|
1361
|
-
const criteria = f.criteria || {};
|
|
1420
|
+
return (res.data.filter || []).filter((f) => !f.action?.forward).map((f) => {
|
|
1362
1421
|
const addLabelIds = f.action?.addLabelIds || [];
|
|
1363
|
-
let matchType = "headerContains";
|
|
1364
|
-
let value = criteria.query || "";
|
|
1365
|
-
if (typeof criteria.from === "string" && criteria.from.startsWith("@")) {
|
|
1366
|
-
matchType = "senderDomain";
|
|
1367
|
-
value = criteria.from.slice(1);
|
|
1368
|
-
} else if (criteria.from) {
|
|
1369
|
-
matchType = "senderAddress";
|
|
1370
|
-
value = criteria.from;
|
|
1371
|
-
} else if (criteria.subject) {
|
|
1372
|
-
matchType = "subjectContains";
|
|
1373
|
-
value = criteria.subject;
|
|
1374
|
-
}
|
|
1375
1422
|
return {
|
|
1376
1423
|
id: f.id || "",
|
|
1377
|
-
|
|
1378
|
-
value,
|
|
1424
|
+
...this.readCriteria(f.criteria),
|
|
1379
1425
|
action: addLabelIds.includes("TRASH") ? "delete" : "moveToJunk",
|
|
1380
1426
|
createdAt: ""
|
|
1381
1427
|
// Gmail's filter API does not expose a creation timestamp
|
|
@@ -1386,30 +1432,71 @@ var GmailAdapter = class {
|
|
|
1386
1432
|
const gmail = this.ensureConnected();
|
|
1387
1433
|
await gmail.users.settings.filters.delete({ userId: "me", id: ruleId });
|
|
1388
1434
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1435
|
+
/**
|
|
1436
|
+
* Gmail only forwards to an address the user has added and confirmed under
|
|
1437
|
+
* Settings, "Forwarding and POP/IMAP". Adding one through the API needs a
|
|
1438
|
+
* scope this app does not request (gmail.settings.sharing), so a missing or
|
|
1439
|
+
* unconfirmed address is answered with the manual step, before Gmail's own
|
|
1440
|
+
* less helpful error.
|
|
1441
|
+
*/
|
|
1442
|
+
async createForwardRule(rule) {
|
|
1443
|
+
const gmail = this.ensureConnected();
|
|
1444
|
+
const target = rule.forwardTo.trim().toLowerCase();
|
|
1445
|
+
const addresses = await gmail.users.settings.forwardingAddresses.list({ userId: "me" });
|
|
1446
|
+
const known = (addresses.data.forwardingAddresses || []).find(
|
|
1447
|
+
(a) => (a.forwardingEmail || "").toLowerCase() === target
|
|
1394
1448
|
);
|
|
1395
|
-
if (
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
lines.push(`Bcc: ${params.bcc.map((c) => c.email).join(", ")}`);
|
|
1449
|
+
if (!known) {
|
|
1450
|
+
throw new Error(
|
|
1451
|
+
`${rule.forwardTo} is not a forwarding address of this Gmail account yet. Add it once in Gmail under Settings, "Forwarding and POP/IMAP", "Add a forwarding address", confirm the email Google sends to it, then run this again.`
|
|
1452
|
+
);
|
|
1400
1453
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1454
|
+
if (known.verificationStatus !== "accepted") {
|
|
1455
|
+
throw new Error(
|
|
1456
|
+
`${rule.forwardTo} was added to this Gmail account but not confirmed yet. Open the confirmation email Google sent to that address, confirm it, then run this again.`
|
|
1457
|
+
);
|
|
1404
1458
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1459
|
+
const existing = (await this.listForwardRules()).find(
|
|
1460
|
+
(r) => r.matchType === rule.matchType && r.value.toLowerCase() === rule.value.toLowerCase() && r.forwardTo.toLowerCase() === target
|
|
1461
|
+
);
|
|
1462
|
+
if (existing) return { id: existing.id, alreadyExisted: true };
|
|
1463
|
+
const action = { forward: known.forwardingEmail };
|
|
1464
|
+
if (!rule.keepInInbox) action.removeLabelIds = ["INBOX"];
|
|
1465
|
+
const res = await gmail.users.settings.filters.create({
|
|
1466
|
+
userId: "me",
|
|
1467
|
+
requestBody: { criteria: this.filterCriteria(rule.matchType, rule.value), action }
|
|
1468
|
+
});
|
|
1469
|
+
return { id: res.data.id || "" };
|
|
1470
|
+
}
|
|
1471
|
+
/** Every filter that forwards, including ones made by hand in Gmail. */
|
|
1472
|
+
async listForwardRules() {
|
|
1473
|
+
const gmail = this.ensureConnected();
|
|
1474
|
+
const res = await gmail.users.settings.filters.list({ userId: "me" });
|
|
1475
|
+
return (res.data.filter || []).filter((f) => f.action?.forward).map((f) => ({
|
|
1476
|
+
id: f.id || "",
|
|
1477
|
+
...this.readCriteria(f.criteria),
|
|
1478
|
+
forwardTo: f.action.forward,
|
|
1479
|
+
keepInInbox: !(f.action.removeLabelIds || []).includes("INBOX"),
|
|
1480
|
+
createdAt: ""
|
|
1481
|
+
// Gmail's filter API does not expose a creation timestamp
|
|
1482
|
+
}));
|
|
1483
|
+
}
|
|
1484
|
+
async deleteForwardRule(ruleId) {
|
|
1485
|
+
const rules = await this.listForwardRules();
|
|
1486
|
+
if (!rules.some((r) => r.id === ruleId)) {
|
|
1487
|
+
throw new Error(`No forwarding rule with id ${ruleId} on this account. Use email_list_forward_rules to see the ids.`);
|
|
1407
1488
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1489
|
+
await this.deleteBlockRule(ruleId);
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Builds the base64url `raw` payload Gmail expects for send, draft create and
|
|
1493
|
+
* draft update: a full MIME message with a text part, an HTML part when one
|
|
1494
|
+
* was given (or a text part derived from HTML-only input), encoded headers
|
|
1495
|
+
* and any attachments. See `buildMimeMessage`.
|
|
1496
|
+
*/
|
|
1497
|
+
async buildRaw(params) {
|
|
1498
|
+
const message = await buildMimeMessage(this.email, params);
|
|
1499
|
+
return message.toString("base64url");
|
|
1413
1500
|
}
|
|
1414
1501
|
};
|
|
1415
1502
|
|
|
@@ -1561,7 +1648,7 @@ function buildGraphFilter(query) {
|
|
|
1561
1648
|
}
|
|
1562
1649
|
|
|
1563
1650
|
// src/providers/outlook/adapter.ts
|
|
1564
|
-
var OutlookAdapter = class {
|
|
1651
|
+
var OutlookAdapter = class _OutlookAdapter {
|
|
1565
1652
|
providerType = ProviderType.Outlook;
|
|
1566
1653
|
client = null;
|
|
1567
1654
|
accountId = "";
|
|
@@ -1929,28 +2016,53 @@ var OutlookAdapter = class {
|
|
|
1929
2016
|
* before this scope was added need to re-run the setup wizard to
|
|
1930
2017
|
* re-consent — old tokens will 403 on this call until then.
|
|
1931
2018
|
*/
|
|
1932
|
-
|
|
1933
|
-
const client = this.ensureClient();
|
|
2019
|
+
ruleConditions(matchType, value) {
|
|
1934
2020
|
const conditions = {};
|
|
1935
|
-
switch (
|
|
2021
|
+
switch (matchType) {
|
|
1936
2022
|
case "senderDomain":
|
|
1937
2023
|
case "senderAddress":
|
|
1938
|
-
conditions.senderContains = [
|
|
2024
|
+
conditions.senderContains = [value];
|
|
1939
2025
|
break;
|
|
1940
2026
|
case "subjectContains":
|
|
1941
|
-
conditions.subjectContains = [
|
|
2027
|
+
conditions.subjectContains = [value];
|
|
1942
2028
|
break;
|
|
1943
2029
|
case "headerContains":
|
|
1944
|
-
conditions.headerContains = [
|
|
2030
|
+
conditions.headerContains = [value];
|
|
1945
2031
|
break;
|
|
1946
2032
|
}
|
|
2033
|
+
return conditions;
|
|
2034
|
+
}
|
|
2035
|
+
// Graph doesn't tag which of our four matchTypes a rule came from:
|
|
2036
|
+
// this reconstruction is a best-effort heuristic for listing/auditing,
|
|
2037
|
+
// not a guaranteed round-trip of what was originally requested.
|
|
2038
|
+
readConditions(conditions) {
|
|
2039
|
+
if (conditions?.senderContains?.length) {
|
|
2040
|
+
const value = conditions.senderContains[0];
|
|
2041
|
+
return { matchType: value.includes("@") ? "senderAddress" : "senderDomain", value };
|
|
2042
|
+
}
|
|
2043
|
+
if (conditions?.subjectContains?.length) return { matchType: "subjectContains", value: conditions.subjectContains[0] };
|
|
2044
|
+
if (conditions?.headerContains?.length) return { matchType: "headerContains", value: conditions.headerContains[0] };
|
|
2045
|
+
return { matchType: "headerContains", value: "" };
|
|
2046
|
+
}
|
|
2047
|
+
/** Addresses a rule sends mail on to, whether by "forward" or "redirect". */
|
|
2048
|
+
static forwardTargets(actions) {
|
|
2049
|
+
return [...actions?.forwardTo || [], ...actions?.redirectTo || [], ...actions?.forwardAsAttachmentTo || []].map((r) => r?.emailAddress?.address).filter((a) => typeof a === "string" && a.length > 0);
|
|
2050
|
+
}
|
|
2051
|
+
async listMessageRules() {
|
|
2052
|
+
const client = this.ensureClient();
|
|
2053
|
+
const response = await client.api("/me/mailFolders/inbox/messageRules").get();
|
|
2054
|
+
return response.value || [];
|
|
2055
|
+
}
|
|
2056
|
+
async createBlockRule(rule) {
|
|
2057
|
+
const client = this.ensureClient();
|
|
2058
|
+
const conditions = this.ruleConditions(rule.matchType, rule.value);
|
|
1947
2059
|
const actions = { stopProcessingRules: true };
|
|
1948
2060
|
if (rule.action === "delete") {
|
|
1949
2061
|
actions.delete = true;
|
|
1950
2062
|
} else {
|
|
1951
2063
|
actions.moveToFolder = await this.resolveFolder("junkemail");
|
|
1952
2064
|
}
|
|
1953
|
-
const existing = await this.
|
|
2065
|
+
const existing = await this.listMessageRules();
|
|
1954
2066
|
const result = await client.api("/me/mailFolders/inbox/messageRules").post({
|
|
1955
2067
|
displayName: `email-mcp: ${rule.matchType} "${rule.value}"`.slice(0, 255),
|
|
1956
2068
|
sequence: existing.length + 1,
|
|
@@ -1961,36 +2073,66 @@ var OutlookAdapter = class {
|
|
|
1961
2073
|
return { id: result.id };
|
|
1962
2074
|
}
|
|
1963
2075
|
async listBlockRules() {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
value = conditions.senderContains[0];
|
|
1972
|
-
matchType = value.includes("@") ? "senderAddress" : "senderDomain";
|
|
1973
|
-
} else if (conditions.subjectContains?.length) {
|
|
1974
|
-
matchType = "subjectContains";
|
|
1975
|
-
value = conditions.subjectContains[0];
|
|
1976
|
-
} else if (conditions.headerContains?.length) {
|
|
1977
|
-
matchType = "headerContains";
|
|
1978
|
-
value = conditions.headerContains[0];
|
|
1979
|
-
}
|
|
1980
|
-
return {
|
|
1981
|
-
id: r.id,
|
|
1982
|
-
matchType,
|
|
1983
|
-
value,
|
|
1984
|
-
action: r.actions?.delete ? "delete" : "moveToJunk",
|
|
1985
|
-
createdAt: ""
|
|
1986
|
-
// Graph's messageRule resource has no creation timestamp field
|
|
1987
|
-
};
|
|
1988
|
-
});
|
|
2076
|
+
return (await this.listMessageRules()).filter((r) => _OutlookAdapter.forwardTargets(r.actions).length === 0).map((r) => ({
|
|
2077
|
+
id: r.id,
|
|
2078
|
+
...this.readConditions(r.conditions),
|
|
2079
|
+
action: r.actions?.delete ? "delete" : "moveToJunk",
|
|
2080
|
+
createdAt: ""
|
|
2081
|
+
// Graph's messageRule resource has no creation timestamp field
|
|
2082
|
+
}));
|
|
1989
2083
|
}
|
|
1990
2084
|
async deleteBlockRule(ruleId) {
|
|
1991
2085
|
const client = this.ensureClient();
|
|
1992
2086
|
await client.api(`/me/mailFolders/inbox/messageRules/${encodeURIComponent(ruleId)}`).delete();
|
|
1993
2087
|
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Outlook forwards to any address without a confirmation step, so the only
|
|
2090
|
+
* check on the target is the allowlist in the tool layer. Needs the same
|
|
2091
|
+
* MailboxSettings.ReadWrite scope as block rules.
|
|
2092
|
+
*/
|
|
2093
|
+
async createForwardRule(rule) {
|
|
2094
|
+
const client = this.ensureClient();
|
|
2095
|
+
const forwardTo = rule.forwardTo.trim();
|
|
2096
|
+
const target = forwardTo.toLowerCase();
|
|
2097
|
+
const all = await this.listMessageRules();
|
|
2098
|
+
const existing = all.find((r) => {
|
|
2099
|
+
const read = this.readConditions(r.conditions);
|
|
2100
|
+
const sameMatch = read.value.toLowerCase() === rule.value.toLowerCase() && (read.matchType === rule.matchType || read.matchType.startsWith("sender") && rule.matchType.startsWith("sender"));
|
|
2101
|
+
return sameMatch && _OutlookAdapter.forwardTargets(r.actions).some((a) => a.toLowerCase() === target);
|
|
2102
|
+
});
|
|
2103
|
+
if (existing) return { id: existing.id, alreadyExisted: true };
|
|
2104
|
+
const actions = { forwardTo: [{ emailAddress: { address: forwardTo } }] };
|
|
2105
|
+
if (!rule.keepInInbox) {
|
|
2106
|
+
actions.moveToFolder = await this.resolveFolder("archive");
|
|
2107
|
+
actions.stopProcessingRules = true;
|
|
2108
|
+
}
|
|
2109
|
+
const result = await client.api("/me/mailFolders/inbox/messageRules").post({
|
|
2110
|
+
displayName: `email-mcp: forward ${rule.matchType} "${rule.value}" to ${forwardTo}`.slice(0, 255),
|
|
2111
|
+
sequence: all.length + 1,
|
|
2112
|
+
isEnabled: true,
|
|
2113
|
+
conditions: this.ruleConditions(rule.matchType, rule.value),
|
|
2114
|
+
actions
|
|
2115
|
+
});
|
|
2116
|
+
return { id: result.id };
|
|
2117
|
+
}
|
|
2118
|
+
/** Every inbox rule that forwards or redirects, including ones made by hand in Outlook. */
|
|
2119
|
+
async listForwardRules() {
|
|
2120
|
+
return (await this.listMessageRules()).filter((r) => _OutlookAdapter.forwardTargets(r.actions).length > 0).map((r) => ({
|
|
2121
|
+
id: r.id,
|
|
2122
|
+
...this.readConditions(r.conditions),
|
|
2123
|
+
forwardTo: _OutlookAdapter.forwardTargets(r.actions).join(", "),
|
|
2124
|
+
keepInInbox: !r.actions?.moveToFolder && !r.actions?.delete,
|
|
2125
|
+
createdAt: ""
|
|
2126
|
+
// Graph's messageRule resource has no creation timestamp field
|
|
2127
|
+
}));
|
|
2128
|
+
}
|
|
2129
|
+
async deleteForwardRule(ruleId) {
|
|
2130
|
+
const rules = await this.listForwardRules();
|
|
2131
|
+
if (!rules.some((r) => r.id === ruleId)) {
|
|
2132
|
+
throw new Error(`No forwarding rule with id ${ruleId} on this account. Use email_list_forward_rules to see the ids.`);
|
|
2133
|
+
}
|
|
2134
|
+
await this.deleteBlockRule(ruleId);
|
|
2135
|
+
}
|
|
1994
2136
|
async getCategories() {
|
|
1995
2137
|
const client = this.ensureClient();
|
|
1996
2138
|
const response = await client.api("/me/outlook/masterCategories").get();
|
|
@@ -2094,14 +2236,15 @@ function createSmtpTransport(email, creds) {
|
|
|
2094
2236
|
});
|
|
2095
2237
|
}
|
|
2096
2238
|
async function sendViaSmtp(transport, from, params) {
|
|
2239
|
+
const { text, html } = normalizeBody(params.body);
|
|
2097
2240
|
const result = await transport.sendMail({
|
|
2098
2241
|
from,
|
|
2099
|
-
to: params.to.map(
|
|
2100
|
-
cc: params.cc?.
|
|
2101
|
-
bcc: params.bcc?.
|
|
2242
|
+
to: params.to.map(toAddress),
|
|
2243
|
+
cc: params.cc?.length ? params.cc.map(toAddress) : void 0,
|
|
2244
|
+
bcc: params.bcc?.length ? params.bcc.map(toAddress) : void 0,
|
|
2102
2245
|
subject: params.subject,
|
|
2103
|
-
text
|
|
2104
|
-
html
|
|
2246
|
+
text,
|
|
2247
|
+
html,
|
|
2105
2248
|
inReplyTo: params.inReplyTo,
|
|
2106
2249
|
references: params.references?.join(" "),
|
|
2107
2250
|
attachments: params.attachments?.map((a) => ({
|
|
@@ -2572,26 +2715,17 @@ var ImapAdapter = class {
|
|
|
2572
2715
|
const messageId = await sendViaSmtp(transport, this.email, params);
|
|
2573
2716
|
return { id: messageId };
|
|
2574
2717
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
lines.push(`Subject: ${params.subject}`);
|
|
2583
|
-
lines.push(`Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
2584
|
-
if (params.inReplyTo) lines.push(`In-Reply-To: ${params.inReplyTo}`);
|
|
2585
|
-
if (params.references?.length) lines.push(`References: ${params.references.join(" ")}`);
|
|
2586
|
-
lines.push("MIME-Version: 1.0");
|
|
2587
|
-
lines.push("Content-Type: text/plain; charset=utf-8");
|
|
2588
|
-
lines.push("");
|
|
2589
|
-
lines.push(params.body.text || "");
|
|
2590
|
-
return lines.join("\r\n");
|
|
2718
|
+
/**
|
|
2719
|
+
* Builds the full MIME message a draft is stored as: text and HTML parts,
|
|
2720
|
+
* Bcc kept so the draft remembers its recipients, encoded headers and any
|
|
2721
|
+
* attachments. See `buildMimeMessage`.
|
|
2722
|
+
*/
|
|
2723
|
+
buildDraftMessage(params) {
|
|
2724
|
+
return buildMimeMessage(this.email, params);
|
|
2591
2725
|
}
|
|
2592
2726
|
async createDraft(params) {
|
|
2593
2727
|
const client = await this.ensureConnected();
|
|
2594
|
-
const rawMessage = this.
|
|
2728
|
+
const rawMessage = await this.buildDraftMessage(params);
|
|
2595
2729
|
const draftsFolder = await this.resolveFolder("Drafts");
|
|
2596
2730
|
const result = await client.append(draftsFolder, rawMessage, ["\\Draft", "\\Seen"]);
|
|
2597
2731
|
if (!result) throw new Error("Failed to append draft");
|
|
@@ -2606,6 +2740,7 @@ var ImapAdapter = class {
|
|
|
2606
2740
|
async updateDraft(draftId, params, sourceFolder) {
|
|
2607
2741
|
const client = await this.ensureConnected();
|
|
2608
2742
|
const draftsFolder = sourceFolder ? await this.resolveFolder(sourceFolder) : await this.resolveFolder("Drafts");
|
|
2743
|
+
const rawMessage = await this.buildDraftMessage(params);
|
|
2609
2744
|
let lock;
|
|
2610
2745
|
try {
|
|
2611
2746
|
lock = await client.getMailboxLock(draftsFolder);
|
|
@@ -2617,7 +2752,6 @@ var ImapAdapter = class {
|
|
|
2617
2752
|
} finally {
|
|
2618
2753
|
lock.release();
|
|
2619
2754
|
}
|
|
2620
|
-
const rawMessage = this.buildDraftRfc2822(params);
|
|
2621
2755
|
const result = await client.append(draftsFolder, rawMessage, ["\\Draft", "\\Seen"]);
|
|
2622
2756
|
if (!result) throw new Error("Failed to append updated draft");
|
|
2623
2757
|
return { id: String(result.uid || result) };
|
|
@@ -3366,6 +3500,9 @@ async function main() {
|
|
|
3366
3500
|
addMore = await askConfirm("Would you like to add another email account?", false);
|
|
3367
3501
|
}
|
|
3368
3502
|
console.log("\nSetup complete! You can now use the email tools in Claude Code.");
|
|
3503
|
+
console.log(
|
|
3504
|
+
"\nemail-mcp is free and open source. Google requires a yearly independent security\naudit (675 US dollars) for the Gmail sign-in it ships. If email-mcp is useful to you,\nyou can help cover it: https://github.com/sponsors/marlinjai"
|
|
3505
|
+
);
|
|
3369
3506
|
}
|
|
3370
3507
|
main().catch((err) => {
|
|
3371
3508
|
console.error(`
|