@ever-works/mailgun-plugin 1.0.5 → 1.0.6
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 +2 -2
- package/dist/index.cjs +227 -3
- package/dist/index.js +227 -3
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -13,11 +13,11 @@ Mailgun email provider plugin for Ever Works (Notifications v2, EW-650).
|
|
|
13
13
|
| `apiKey` | yes | `MAILGUN_API_KEY` | yes |
|
|
14
14
|
| `domain` | no | `MAILGUN_DOMAIN` | yes |
|
|
15
15
|
| `region` | no | `MAILGUN_REGION` (`us`/`eu`) | no (us) |
|
|
16
|
-
| `webhookSigningKey` | yes | `MAILGUN_WEBHOOK_SIGNING_KEY` |
|
|
16
|
+
| `webhookSigningKey` | yes | `MAILGUN_WEBHOOK_SIGNING_KEY` | inbound |
|
|
17
17
|
|
|
18
18
|
## Notes
|
|
19
19
|
|
|
20
20
|
- Outbound is built on the official **`mailgun.js`** SDK (`messages.create`); metadata is forwarded as Mailgun `v:` custom variables.
|
|
21
21
|
- Inbound HMAC verification uses Node `crypto` — Mailgun ships no SDK helper for inbound-route signature checking.
|
|
22
|
-
- Inbound verifies the Mailgun **HMAC-SHA256** signature — `HMAC(signingKey, timestamp + token)` compared in constant time — and decodes both JSON and form-urlencoded webhook shapes. When no signing key is set,
|
|
22
|
+
- Inbound verifies the Mailgun **HMAC-SHA256** signature — `HMAC(signingKey, timestamp + token)` compared in constant time — and decodes both JSON and form-urlencoded webhook shapes. When no signing key is set, inbound webhooks are **refused** (`401`); only `EMAIL_INBOUND_ALLOW_UNSIGNED=true` (that exact string, for a local stack with no public webhook URL) accepts them unsigned.
|
|
23
23
|
- `parseInboundWebhook` maps Mailgun's parsed fields (`sender`/`recipient`/`subject`/`body-plain`/`body-html`). Multipart attachment extraction requires a multipart parser at the controller layer and is left empty here.
|
package/dist/index.cjs
CHANGED
|
@@ -103,10 +103,14 @@ var USER_SELECTABLE_WORK_KINDS = [
|
|
|
103
103
|
"repo",
|
|
104
104
|
"app"
|
|
105
105
|
];
|
|
106
|
-
var WORK_KINDS = [...USER_SELECTABLE_WORK_KINDS, "company", "campaign", "default"];
|
|
106
|
+
var WORK_KINDS = [...USER_SELECTABLE_WORK_KINDS, "company", "campaign", "project", "default"];
|
|
107
107
|
new Set(WORK_KINDS);
|
|
108
108
|
var GOAL_KINDS = ["metric", "delivery"];
|
|
109
109
|
new Set(GOAL_KINDS);
|
|
110
|
+
var RECORD_VISIBILITIES = ["private", "team", "organization"];
|
|
111
|
+
new Set(RECORD_VISIBILITIES);
|
|
112
|
+
var RECORD_LIST_VIEWS = ["mine", "shared", "organization"];
|
|
113
|
+
new Set(RECORD_LIST_VIEWS);
|
|
110
114
|
var VERSION = "1.0.0";
|
|
111
115
|
var GTM_SKILLS = [
|
|
112
116
|
// ── research ────────────────────────────────────────────────────────
|
|
@@ -812,6 +816,155 @@ var ACTION_CATEGORIES = Object.freeze([
|
|
|
812
816
|
Object.freeze(
|
|
813
817
|
ACTION_CATEGORIES.filter((c) => c !== "read.internal")
|
|
814
818
|
);
|
|
819
|
+
var DOMAIN_EVENT_CATALOG = [
|
|
820
|
+
// EW-P2-6 — `org:write` (and the app) create Organizations.
|
|
821
|
+
{ type: "organization.created", domain: "organization", aggregateType: "organization", phase: "P2" },
|
|
822
|
+
{ type: "organization.updated", domain: "organization", aggregateType: "organization", phase: "P1" },
|
|
823
|
+
{
|
|
824
|
+
type: "organization.member.added",
|
|
825
|
+
domain: "organization",
|
|
826
|
+
aggregateType: "organization",
|
|
827
|
+
phase: "P1",
|
|
828
|
+
child: "member"
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
type: "organization.member.updated",
|
|
832
|
+
domain: "organization",
|
|
833
|
+
aggregateType: "organization",
|
|
834
|
+
phase: "P1",
|
|
835
|
+
child: "member"
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
type: "organization.member.removed",
|
|
839
|
+
domain: "organization",
|
|
840
|
+
aggregateType: "organization",
|
|
841
|
+
phase: "P1",
|
|
842
|
+
child: "member"
|
|
843
|
+
},
|
|
844
|
+
{ type: "agent.created", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
845
|
+
{ type: "agent.updated", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
846
|
+
{ type: "agent.status_changed", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
847
|
+
{ type: "agent.archived", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
848
|
+
{ type: "agent.deleted", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
849
|
+
{ type: "team.created", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
850
|
+
{ type: "team.updated", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
851
|
+
{ type: "team.deleted", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
852
|
+
{ type: "team.member.added", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
853
|
+
{ type: "team.member.updated", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
854
|
+
{ type: "team.member.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
855
|
+
{ type: "team.resource.added", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
856
|
+
{ type: "team.resource.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
857
|
+
{ type: "work.created", domain: "works", aggregateType: "work", phase: "P1" },
|
|
858
|
+
{ type: "work.updated", domain: "works", aggregateType: "work", phase: "P1" },
|
|
859
|
+
{ type: "work.archived", domain: "works", aggregateType: "work", phase: "P1" },
|
|
860
|
+
{ type: "work.deleted", domain: "works", aggregateType: "work", phase: "P1" },
|
|
861
|
+
{ type: "work.member.added", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
862
|
+
{ type: "work.member.updated", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
863
|
+
{ type: "work.member.removed", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
864
|
+
{ type: "mission.created", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
865
|
+
{ type: "mission.updated", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
866
|
+
{ type: "mission.status_changed", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
867
|
+
{ type: "mission.deleted", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
868
|
+
{ type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
|
|
869
|
+
{ type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
|
|
870
|
+
{ type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
|
|
871
|
+
{ type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
|
|
872
|
+
{ type: "goal.created", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
873
|
+
{ type: "goal.updated", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
874
|
+
{ type: "goal.status_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
875
|
+
{ type: "goal.archived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
876
|
+
{ type: "goal.unarchived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
877
|
+
{ type: "goal.deleted", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
878
|
+
{ type: "goal.progress_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
879
|
+
{ type: "idea.created", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
880
|
+
{ type: "idea.updated", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
881
|
+
{ type: "idea.status_changed", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
882
|
+
{ type: "idea.deleted", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
883
|
+
{ type: "task.created", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
884
|
+
{ type: "task.updated", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
885
|
+
{ type: "task.transitioned", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
886
|
+
{ type: "task.deleted", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
887
|
+
{ type: "task.assignee.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
888
|
+
{ type: "task.assignee.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
889
|
+
{ type: "task.watcher.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
890
|
+
{ type: "task.watcher.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
891
|
+
{ type: "task.block.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
892
|
+
{ type: "task.block.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
893
|
+
{ type: "task.relation.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
894
|
+
{ type: "task.relation.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
895
|
+
{ type: "task.comment.created", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
896
|
+
{ type: "task.comment.updated", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
897
|
+
{ type: "task.comment.deleted", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
898
|
+
{ type: "task.attachment.added", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
899
|
+
{ type: "task.attachment.removed", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
900
|
+
{ type: "task.runtime_updated", domain: "tasks", aggregateType: "task", phase: "P1", optIn: true },
|
|
901
|
+
{ type: "inbox.item.created", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
902
|
+
{ type: "inbox.item.answered", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
903
|
+
{ type: "inbox.item.state_changed", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
904
|
+
{ type: "inbox.item.deleted", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
905
|
+
{ type: "kb.document.created", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
906
|
+
{ type: "kb.document.updated", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
907
|
+
{ type: "kb.document.moved", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
908
|
+
{ type: "kb.document.archived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
909
|
+
{ type: "kb.document.unarchived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
910
|
+
{ type: "kb.document.deleted", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
911
|
+
{ type: "kb.document.review_changed", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
912
|
+
{ type: "kb.folder.created", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
913
|
+
{ type: "kb.folder.updated", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
914
|
+
{ type: "kb.folder.deleted", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
915
|
+
{ type: "email.message.received", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
916
|
+
{ type: "email.message.sent", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
917
|
+
{ type: "email.message.status_changed", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
918
|
+
{ type: "task.visibility_changed", domain: "visibility", aggregateType: "task", phase: "P1" },
|
|
919
|
+
{ type: "agent.visibility_changed", domain: "visibility", aggregateType: "agent", phase: "P1" },
|
|
920
|
+
{ type: "goal.visibility_changed", domain: "visibility", aggregateType: "goal", phase: "P2" },
|
|
921
|
+
{ type: "mission.visibility_changed", domain: "visibility", aggregateType: "mission", phase: "P2" },
|
|
922
|
+
{ type: "idea.visibility_changed", domain: "visibility", aggregateType: "idea", phase: "P2" }
|
|
923
|
+
];
|
|
924
|
+
DOMAIN_EVENT_CATALOG.map((entry) => entry.type);
|
|
925
|
+
new Map(
|
|
926
|
+
DOMAIN_EVENT_CATALOG.map((entry) => [entry.type, entry])
|
|
927
|
+
);
|
|
928
|
+
var EMAIL_MESSAGE_ID_MAX_LENGTH = 255;
|
|
929
|
+
var EMAIL_REFERENCES_MAX_IDS = 50;
|
|
930
|
+
function normalizeMessageId(raw) {
|
|
931
|
+
if (typeof raw !== "string") return void 0;
|
|
932
|
+
let value = raw.trim();
|
|
933
|
+
const open = value.indexOf("<");
|
|
934
|
+
if (open !== -1) {
|
|
935
|
+
const close = value.indexOf(">", open + 1);
|
|
936
|
+
value = close === -1 ? value.slice(open + 1) : value.slice(open + 1, close);
|
|
937
|
+
}
|
|
938
|
+
value = value.trim();
|
|
939
|
+
if (!value || value.length > EMAIL_MESSAGE_ID_MAX_LENGTH) return void 0;
|
|
940
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
941
|
+
const code = value.charCodeAt(i);
|
|
942
|
+
if (code <= 32 || code === 127 || value[i] === "<" || value[i] === ">") return void 0;
|
|
943
|
+
}
|
|
944
|
+
return value;
|
|
945
|
+
}
|
|
946
|
+
function parseMessageIdList(raw) {
|
|
947
|
+
if (raw == null) return [];
|
|
948
|
+
const text = Array.isArray(raw) ? raw.join(" ") : String(raw);
|
|
949
|
+
const ids = [];
|
|
950
|
+
if (text.includes("<")) {
|
|
951
|
+
let open = text.indexOf("<");
|
|
952
|
+
while (open !== -1) {
|
|
953
|
+
const close = text.indexOf(">", open + 1);
|
|
954
|
+
if (close === -1) break;
|
|
955
|
+
const id = normalizeMessageId(text.slice(open + 1, close));
|
|
956
|
+
if (id) ids.push(id);
|
|
957
|
+
open = text.indexOf("<", close + 1);
|
|
958
|
+
}
|
|
959
|
+
} else {
|
|
960
|
+
for (const part of text.split(/[\s,]+/)) {
|
|
961
|
+
const id = normalizeMessageId(part);
|
|
962
|
+
if (id) ids.push(id);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
const unique = [...new Set(ids)];
|
|
966
|
+
return unique.slice(Math.max(0, unique.length - EMAIL_REFERENCES_MAX_IDS));
|
|
967
|
+
}
|
|
815
968
|
var PLUGIN_CAPABILITIES = {
|
|
816
969
|
// Notifications v2 (EW-650) — Email Providers. Plugins MAY declare
|
|
817
970
|
// `EMAIL_OUTBOUND`, `EMAIL_INBOUND`, or both. See
|
|
@@ -844,6 +997,10 @@ function resolveSigningKey(options) {
|
|
|
844
997
|
const fromEnv = process.env.MAILGUN_WEBHOOK_SIGNING_KEY;
|
|
845
998
|
return fromEnv && fromEnv.length > 0 ? fromEnv : void 0;
|
|
846
999
|
}
|
|
1000
|
+
var EMAIL_INBOUND_ALLOW_UNSIGNED_ENV = "EMAIL_INBOUND_ALLOW_UNSIGNED";
|
|
1001
|
+
function unsignedInboundAllowed() {
|
|
1002
|
+
return process.env[EMAIL_INBOUND_ALLOW_UNSIGNED_ENV] === "true";
|
|
1003
|
+
}
|
|
847
1004
|
function decodeBody(rawBody) {
|
|
848
1005
|
const text = rawBody.toString("utf8");
|
|
849
1006
|
try {
|
|
@@ -870,6 +1027,31 @@ function angleAddress(value) {
|
|
|
870
1027
|
}
|
|
871
1028
|
return null;
|
|
872
1029
|
}
|
|
1030
|
+
function splitAddressList(raw) {
|
|
1031
|
+
const parts = [];
|
|
1032
|
+
let current = "";
|
|
1033
|
+
let inQuote = false;
|
|
1034
|
+
let inAngle = false;
|
|
1035
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
1036
|
+
const ch = raw[i];
|
|
1037
|
+
if (ch === "\\" && inQuote && i + 1 < raw.length) {
|
|
1038
|
+
current += ch + raw[i + 1];
|
|
1039
|
+
i += 1;
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
if (ch === '"' && !inAngle) inQuote = !inQuote;
|
|
1043
|
+
else if (ch === "<" && !inQuote) inAngle = true;
|
|
1044
|
+
else if (ch === ">" && !inQuote) inAngle = false;
|
|
1045
|
+
if (ch === "," && !inQuote && !inAngle) {
|
|
1046
|
+
parts.push(current);
|
|
1047
|
+
current = "";
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
current += ch;
|
|
1051
|
+
}
|
|
1052
|
+
parts.push(current);
|
|
1053
|
+
return parts;
|
|
1054
|
+
}
|
|
873
1055
|
function extractSignature(body) {
|
|
874
1056
|
const nested = body.signature;
|
|
875
1057
|
if (nested && typeof nested === "object") {
|
|
@@ -922,6 +1104,7 @@ var MailgunPlugin = class {
|
|
|
922
1104
|
const mg = mailgun.client({ username: "api", key: apiKey, url: resolveApiUrl(options) });
|
|
923
1105
|
const customVars = {};
|
|
924
1106
|
for (const [k, v] of Object.entries(input.metadata ?? {})) customVars[`v:${k}`] = v;
|
|
1107
|
+
for (const [k, v] of Object.entries(input.headers ?? {})) customVars[`h:${k}`] = v;
|
|
925
1108
|
let id;
|
|
926
1109
|
try {
|
|
927
1110
|
const res = await mg.messages.create(domain, {
|
|
@@ -965,7 +1148,17 @@ var MailgunPlugin = class {
|
|
|
965
1148
|
}
|
|
966
1149
|
verifyWebhookSignature(rawBody, _headers, options) {
|
|
967
1150
|
const signingKey = resolveSigningKey(options);
|
|
968
|
-
if (!signingKey)
|
|
1151
|
+
if (!signingKey) {
|
|
1152
|
+
if (!unsignedInboundAllowed()) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
`Mailgun inbound: no \`webhookSigningKey\`/MAILGUN_WEBHOOK_SIGNING_KEY configured; refusing an unsigned webhook (${EMAIL_INBOUND_ALLOW_UNSIGNED_ENV}=true accepts it on a local stack only).`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
console.warn(
|
|
1158
|
+
`[mailgun] inbound webhook accepted WITHOUT signature verification: no \`webhookSigningKey\`/MAILGUN_WEBHOOK_SIGNING_KEY configured and ${EMAIL_INBOUND_ALLOW_UNSIGNED_ENV}=true. Configure a signing key to authenticate inbound mail.`
|
|
1159
|
+
);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
969
1162
|
const sig = extractSignature(decodeBody(rawBody));
|
|
970
1163
|
if (!sig) {
|
|
971
1164
|
throw new Error("Mailgun inbound: missing timestamp/token/signature.");
|
|
@@ -992,11 +1185,39 @@ var MailgunPlugin = class {
|
|
|
992
1185
|
const recipient = str("recipient") ?? str("To") ?? "";
|
|
993
1186
|
const messageId = (str("Message-Id") ?? str("message-id") ?? `mailgun-${Date.now()}`).replace(/^<|>$/g, "");
|
|
994
1187
|
const tsSeconds = Number(str("timestamp"));
|
|
1188
|
+
const header = (name) => {
|
|
1189
|
+
for (const [key, value] of Object.entries(body)) {
|
|
1190
|
+
if (key.toLowerCase() === name && typeof value === "string") return value;
|
|
1191
|
+
}
|
|
1192
|
+
const raw = body["message-headers"];
|
|
1193
|
+
let pairs = raw;
|
|
1194
|
+
if (typeof raw === "string") {
|
|
1195
|
+
try {
|
|
1196
|
+
pairs = JSON.parse(raw);
|
|
1197
|
+
} catch {
|
|
1198
|
+
pairs = null;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (Array.isArray(pairs)) {
|
|
1202
|
+
for (const pair of pairs) {
|
|
1203
|
+
if (Array.isArray(pair) && typeof pair[0] === "string" && typeof pair[1] === "string" && pair[0].toLowerCase() === name) {
|
|
1204
|
+
return pair[1];
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return void 0;
|
|
1209
|
+
};
|
|
1210
|
+
const messageIdHeader = normalizeMessageId(header("message-id"));
|
|
1211
|
+
const cc = splitAddressList(header("cc") ?? "").map((part) => (angleAddress(part.trim()) ?? part).trim()).filter((address) => address.length > 0 && address.includes("@"));
|
|
1212
|
+
const inReplyTo = parseMessageIdList(header("in-reply-to"))[0];
|
|
1213
|
+
const references = parseMessageIdList(header("references"));
|
|
995
1214
|
return {
|
|
996
1215
|
provider: this.id,
|
|
997
1216
|
providerMessageId: messageId,
|
|
998
1217
|
from: str("sender") ?? str("from") ?? "",
|
|
999
1218
|
to: parseAddressList(recipient),
|
|
1219
|
+
// EW-P4-3 — Cc is recorded (never routed on).
|
|
1220
|
+
...cc.length > 0 ? { cc } : {},
|
|
1000
1221
|
subject: str("subject") ?? "",
|
|
1001
1222
|
bodyText: str("body-plain") ?? str("stripped-text") ?? "",
|
|
1002
1223
|
bodyHtml: str("body-html") ?? str("stripped-html"),
|
|
@@ -1005,7 +1226,10 @@ var MailgunPlugin = class {
|
|
|
1005
1226
|
metadata: {
|
|
1006
1227
|
recipient,
|
|
1007
1228
|
signatureVerified: Boolean(resolveSigningKey(_options))
|
|
1008
|
-
}
|
|
1229
|
+
},
|
|
1230
|
+
...messageIdHeader ? { messageIdHeader } : {},
|
|
1231
|
+
...inReplyTo ? { inReplyTo } : {},
|
|
1232
|
+
...references.length > 0 ? { references } : {}
|
|
1009
1233
|
};
|
|
1010
1234
|
}
|
|
1011
1235
|
};
|
package/dist/index.js
CHANGED
|
@@ -96,10 +96,14 @@ var USER_SELECTABLE_WORK_KINDS = [
|
|
|
96
96
|
"repo",
|
|
97
97
|
"app"
|
|
98
98
|
];
|
|
99
|
-
var WORK_KINDS = [...USER_SELECTABLE_WORK_KINDS, "company", "campaign", "default"];
|
|
99
|
+
var WORK_KINDS = [...USER_SELECTABLE_WORK_KINDS, "company", "campaign", "project", "default"];
|
|
100
100
|
new Set(WORK_KINDS);
|
|
101
101
|
var GOAL_KINDS = ["metric", "delivery"];
|
|
102
102
|
new Set(GOAL_KINDS);
|
|
103
|
+
var RECORD_VISIBILITIES = ["private", "team", "organization"];
|
|
104
|
+
new Set(RECORD_VISIBILITIES);
|
|
105
|
+
var RECORD_LIST_VIEWS = ["mine", "shared", "organization"];
|
|
106
|
+
new Set(RECORD_LIST_VIEWS);
|
|
103
107
|
var VERSION = "1.0.0";
|
|
104
108
|
var GTM_SKILLS = [
|
|
105
109
|
// ── research ────────────────────────────────────────────────────────
|
|
@@ -805,6 +809,155 @@ var ACTION_CATEGORIES = Object.freeze([
|
|
|
805
809
|
Object.freeze(
|
|
806
810
|
ACTION_CATEGORIES.filter((c) => c !== "read.internal")
|
|
807
811
|
);
|
|
812
|
+
var DOMAIN_EVENT_CATALOG = [
|
|
813
|
+
// EW-P2-6 — `org:write` (and the app) create Organizations.
|
|
814
|
+
{ type: "organization.created", domain: "organization", aggregateType: "organization", phase: "P2" },
|
|
815
|
+
{ type: "organization.updated", domain: "organization", aggregateType: "organization", phase: "P1" },
|
|
816
|
+
{
|
|
817
|
+
type: "organization.member.added",
|
|
818
|
+
domain: "organization",
|
|
819
|
+
aggregateType: "organization",
|
|
820
|
+
phase: "P1",
|
|
821
|
+
child: "member"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
type: "organization.member.updated",
|
|
825
|
+
domain: "organization",
|
|
826
|
+
aggregateType: "organization",
|
|
827
|
+
phase: "P1",
|
|
828
|
+
child: "member"
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
type: "organization.member.removed",
|
|
832
|
+
domain: "organization",
|
|
833
|
+
aggregateType: "organization",
|
|
834
|
+
phase: "P1",
|
|
835
|
+
child: "member"
|
|
836
|
+
},
|
|
837
|
+
{ type: "agent.created", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
838
|
+
{ type: "agent.updated", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
839
|
+
{ type: "agent.status_changed", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
840
|
+
{ type: "agent.archived", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
841
|
+
{ type: "agent.deleted", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
842
|
+
{ type: "team.created", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
843
|
+
{ type: "team.updated", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
844
|
+
{ type: "team.deleted", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
845
|
+
{ type: "team.member.added", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
846
|
+
{ type: "team.member.updated", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
847
|
+
{ type: "team.member.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
848
|
+
{ type: "team.resource.added", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
849
|
+
{ type: "team.resource.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
850
|
+
{ type: "work.created", domain: "works", aggregateType: "work", phase: "P1" },
|
|
851
|
+
{ type: "work.updated", domain: "works", aggregateType: "work", phase: "P1" },
|
|
852
|
+
{ type: "work.archived", domain: "works", aggregateType: "work", phase: "P1" },
|
|
853
|
+
{ type: "work.deleted", domain: "works", aggregateType: "work", phase: "P1" },
|
|
854
|
+
{ type: "work.member.added", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
855
|
+
{ type: "work.member.updated", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
856
|
+
{ type: "work.member.removed", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
857
|
+
{ type: "mission.created", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
858
|
+
{ type: "mission.updated", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
859
|
+
{ type: "mission.status_changed", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
860
|
+
{ type: "mission.deleted", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
861
|
+
{ type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
|
|
862
|
+
{ type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "goal" },
|
|
863
|
+
{ type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
|
|
864
|
+
{ type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2", child: "work" },
|
|
865
|
+
{ type: "goal.created", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
866
|
+
{ type: "goal.updated", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
867
|
+
{ type: "goal.status_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
868
|
+
{ type: "goal.archived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
869
|
+
{ type: "goal.unarchived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
870
|
+
{ type: "goal.deleted", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
871
|
+
{ type: "goal.progress_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
872
|
+
{ type: "idea.created", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
873
|
+
{ type: "idea.updated", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
874
|
+
{ type: "idea.status_changed", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
875
|
+
{ type: "idea.deleted", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
876
|
+
{ type: "task.created", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
877
|
+
{ type: "task.updated", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
878
|
+
{ type: "task.transitioned", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
879
|
+
{ type: "task.deleted", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
880
|
+
{ type: "task.assignee.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
881
|
+
{ type: "task.assignee.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
882
|
+
{ type: "task.watcher.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
883
|
+
{ type: "task.watcher.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
884
|
+
{ type: "task.block.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
885
|
+
{ type: "task.block.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
886
|
+
{ type: "task.relation.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
887
|
+
{ type: "task.relation.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
888
|
+
{ type: "task.comment.created", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
889
|
+
{ type: "task.comment.updated", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
890
|
+
{ type: "task.comment.deleted", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
891
|
+
{ type: "task.attachment.added", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
892
|
+
{ type: "task.attachment.removed", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
893
|
+
{ type: "task.runtime_updated", domain: "tasks", aggregateType: "task", phase: "P1", optIn: true },
|
|
894
|
+
{ type: "inbox.item.created", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
895
|
+
{ type: "inbox.item.answered", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
896
|
+
{ type: "inbox.item.state_changed", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
897
|
+
{ type: "inbox.item.deleted", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
898
|
+
{ type: "kb.document.created", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
899
|
+
{ type: "kb.document.updated", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
900
|
+
{ type: "kb.document.moved", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
901
|
+
{ type: "kb.document.archived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
902
|
+
{ type: "kb.document.unarchived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
903
|
+
{ type: "kb.document.deleted", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
904
|
+
{ type: "kb.document.review_changed", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
905
|
+
{ type: "kb.folder.created", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
906
|
+
{ type: "kb.folder.updated", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
907
|
+
{ type: "kb.folder.deleted", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
908
|
+
{ type: "email.message.received", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
909
|
+
{ type: "email.message.sent", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
910
|
+
{ type: "email.message.status_changed", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
911
|
+
{ type: "task.visibility_changed", domain: "visibility", aggregateType: "task", phase: "P1" },
|
|
912
|
+
{ type: "agent.visibility_changed", domain: "visibility", aggregateType: "agent", phase: "P1" },
|
|
913
|
+
{ type: "goal.visibility_changed", domain: "visibility", aggregateType: "goal", phase: "P2" },
|
|
914
|
+
{ type: "mission.visibility_changed", domain: "visibility", aggregateType: "mission", phase: "P2" },
|
|
915
|
+
{ type: "idea.visibility_changed", domain: "visibility", aggregateType: "idea", phase: "P2" }
|
|
916
|
+
];
|
|
917
|
+
DOMAIN_EVENT_CATALOG.map((entry) => entry.type);
|
|
918
|
+
new Map(
|
|
919
|
+
DOMAIN_EVENT_CATALOG.map((entry) => [entry.type, entry])
|
|
920
|
+
);
|
|
921
|
+
var EMAIL_MESSAGE_ID_MAX_LENGTH = 255;
|
|
922
|
+
var EMAIL_REFERENCES_MAX_IDS = 50;
|
|
923
|
+
function normalizeMessageId(raw) {
|
|
924
|
+
if (typeof raw !== "string") return void 0;
|
|
925
|
+
let value = raw.trim();
|
|
926
|
+
const open = value.indexOf("<");
|
|
927
|
+
if (open !== -1) {
|
|
928
|
+
const close = value.indexOf(">", open + 1);
|
|
929
|
+
value = close === -1 ? value.slice(open + 1) : value.slice(open + 1, close);
|
|
930
|
+
}
|
|
931
|
+
value = value.trim();
|
|
932
|
+
if (!value || value.length > EMAIL_MESSAGE_ID_MAX_LENGTH) return void 0;
|
|
933
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
934
|
+
const code = value.charCodeAt(i);
|
|
935
|
+
if (code <= 32 || code === 127 || value[i] === "<" || value[i] === ">") return void 0;
|
|
936
|
+
}
|
|
937
|
+
return value;
|
|
938
|
+
}
|
|
939
|
+
function parseMessageIdList(raw) {
|
|
940
|
+
if (raw == null) return [];
|
|
941
|
+
const text = Array.isArray(raw) ? raw.join(" ") : String(raw);
|
|
942
|
+
const ids = [];
|
|
943
|
+
if (text.includes("<")) {
|
|
944
|
+
let open = text.indexOf("<");
|
|
945
|
+
while (open !== -1) {
|
|
946
|
+
const close = text.indexOf(">", open + 1);
|
|
947
|
+
if (close === -1) break;
|
|
948
|
+
const id = normalizeMessageId(text.slice(open + 1, close));
|
|
949
|
+
if (id) ids.push(id);
|
|
950
|
+
open = text.indexOf("<", close + 1);
|
|
951
|
+
}
|
|
952
|
+
} else {
|
|
953
|
+
for (const part of text.split(/[\s,]+/)) {
|
|
954
|
+
const id = normalizeMessageId(part);
|
|
955
|
+
if (id) ids.push(id);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const unique = [...new Set(ids)];
|
|
959
|
+
return unique.slice(Math.max(0, unique.length - EMAIL_REFERENCES_MAX_IDS));
|
|
960
|
+
}
|
|
808
961
|
var PLUGIN_CAPABILITIES = {
|
|
809
962
|
// Notifications v2 (EW-650) — Email Providers. Plugins MAY declare
|
|
810
963
|
// `EMAIL_OUTBOUND`, `EMAIL_INBOUND`, or both. See
|
|
@@ -837,6 +990,10 @@ function resolveSigningKey(options) {
|
|
|
837
990
|
const fromEnv = process.env.MAILGUN_WEBHOOK_SIGNING_KEY;
|
|
838
991
|
return fromEnv && fromEnv.length > 0 ? fromEnv : void 0;
|
|
839
992
|
}
|
|
993
|
+
var EMAIL_INBOUND_ALLOW_UNSIGNED_ENV = "EMAIL_INBOUND_ALLOW_UNSIGNED";
|
|
994
|
+
function unsignedInboundAllowed() {
|
|
995
|
+
return process.env[EMAIL_INBOUND_ALLOW_UNSIGNED_ENV] === "true";
|
|
996
|
+
}
|
|
840
997
|
function decodeBody(rawBody) {
|
|
841
998
|
const text = rawBody.toString("utf8");
|
|
842
999
|
try {
|
|
@@ -863,6 +1020,31 @@ function angleAddress(value) {
|
|
|
863
1020
|
}
|
|
864
1021
|
return null;
|
|
865
1022
|
}
|
|
1023
|
+
function splitAddressList(raw) {
|
|
1024
|
+
const parts = [];
|
|
1025
|
+
let current = "";
|
|
1026
|
+
let inQuote = false;
|
|
1027
|
+
let inAngle = false;
|
|
1028
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
1029
|
+
const ch = raw[i];
|
|
1030
|
+
if (ch === "\\" && inQuote && i + 1 < raw.length) {
|
|
1031
|
+
current += ch + raw[i + 1];
|
|
1032
|
+
i += 1;
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
if (ch === '"' && !inAngle) inQuote = !inQuote;
|
|
1036
|
+
else if (ch === "<" && !inQuote) inAngle = true;
|
|
1037
|
+
else if (ch === ">" && !inQuote) inAngle = false;
|
|
1038
|
+
if (ch === "," && !inQuote && !inAngle) {
|
|
1039
|
+
parts.push(current);
|
|
1040
|
+
current = "";
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
current += ch;
|
|
1044
|
+
}
|
|
1045
|
+
parts.push(current);
|
|
1046
|
+
return parts;
|
|
1047
|
+
}
|
|
866
1048
|
function extractSignature(body) {
|
|
867
1049
|
const nested = body.signature;
|
|
868
1050
|
if (nested && typeof nested === "object") {
|
|
@@ -915,6 +1097,7 @@ var MailgunPlugin = class {
|
|
|
915
1097
|
const mg = mailgun.client({ username: "api", key: apiKey, url: resolveApiUrl(options) });
|
|
916
1098
|
const customVars = {};
|
|
917
1099
|
for (const [k, v] of Object.entries(input.metadata ?? {})) customVars[`v:${k}`] = v;
|
|
1100
|
+
for (const [k, v] of Object.entries(input.headers ?? {})) customVars[`h:${k}`] = v;
|
|
918
1101
|
let id;
|
|
919
1102
|
try {
|
|
920
1103
|
const res = await mg.messages.create(domain, {
|
|
@@ -958,7 +1141,17 @@ var MailgunPlugin = class {
|
|
|
958
1141
|
}
|
|
959
1142
|
verifyWebhookSignature(rawBody, _headers, options) {
|
|
960
1143
|
const signingKey = resolveSigningKey(options);
|
|
961
|
-
if (!signingKey)
|
|
1144
|
+
if (!signingKey) {
|
|
1145
|
+
if (!unsignedInboundAllowed()) {
|
|
1146
|
+
throw new Error(
|
|
1147
|
+
`Mailgun inbound: no \`webhookSigningKey\`/MAILGUN_WEBHOOK_SIGNING_KEY configured; refusing an unsigned webhook (${EMAIL_INBOUND_ALLOW_UNSIGNED_ENV}=true accepts it on a local stack only).`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
console.warn(
|
|
1151
|
+
`[mailgun] inbound webhook accepted WITHOUT signature verification: no \`webhookSigningKey\`/MAILGUN_WEBHOOK_SIGNING_KEY configured and ${EMAIL_INBOUND_ALLOW_UNSIGNED_ENV}=true. Configure a signing key to authenticate inbound mail.`
|
|
1152
|
+
);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
962
1155
|
const sig = extractSignature(decodeBody(rawBody));
|
|
963
1156
|
if (!sig) {
|
|
964
1157
|
throw new Error("Mailgun inbound: missing timestamp/token/signature.");
|
|
@@ -985,11 +1178,39 @@ var MailgunPlugin = class {
|
|
|
985
1178
|
const recipient = str("recipient") ?? str("To") ?? "";
|
|
986
1179
|
const messageId = (str("Message-Id") ?? str("message-id") ?? `mailgun-${Date.now()}`).replace(/^<|>$/g, "");
|
|
987
1180
|
const tsSeconds = Number(str("timestamp"));
|
|
1181
|
+
const header = (name) => {
|
|
1182
|
+
for (const [key, value] of Object.entries(body)) {
|
|
1183
|
+
if (key.toLowerCase() === name && typeof value === "string") return value;
|
|
1184
|
+
}
|
|
1185
|
+
const raw = body["message-headers"];
|
|
1186
|
+
let pairs = raw;
|
|
1187
|
+
if (typeof raw === "string") {
|
|
1188
|
+
try {
|
|
1189
|
+
pairs = JSON.parse(raw);
|
|
1190
|
+
} catch {
|
|
1191
|
+
pairs = null;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
if (Array.isArray(pairs)) {
|
|
1195
|
+
for (const pair of pairs) {
|
|
1196
|
+
if (Array.isArray(pair) && typeof pair[0] === "string" && typeof pair[1] === "string" && pair[0].toLowerCase() === name) {
|
|
1197
|
+
return pair[1];
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return void 0;
|
|
1202
|
+
};
|
|
1203
|
+
const messageIdHeader = normalizeMessageId(header("message-id"));
|
|
1204
|
+
const cc = splitAddressList(header("cc") ?? "").map((part) => (angleAddress(part.trim()) ?? part).trim()).filter((address) => address.length > 0 && address.includes("@"));
|
|
1205
|
+
const inReplyTo = parseMessageIdList(header("in-reply-to"))[0];
|
|
1206
|
+
const references = parseMessageIdList(header("references"));
|
|
988
1207
|
return {
|
|
989
1208
|
provider: this.id,
|
|
990
1209
|
providerMessageId: messageId,
|
|
991
1210
|
from: str("sender") ?? str("from") ?? "",
|
|
992
1211
|
to: parseAddressList(recipient),
|
|
1212
|
+
// EW-P4-3 — Cc is recorded (never routed on).
|
|
1213
|
+
...cc.length > 0 ? { cc } : {},
|
|
993
1214
|
subject: str("subject") ?? "",
|
|
994
1215
|
bodyText: str("body-plain") ?? str("stripped-text") ?? "",
|
|
995
1216
|
bodyHtml: str("body-html") ?? str("stripped-html"),
|
|
@@ -998,7 +1219,10 @@ var MailgunPlugin = class {
|
|
|
998
1219
|
metadata: {
|
|
999
1220
|
recipient,
|
|
1000
1221
|
signatureVerified: Boolean(resolveSigningKey(_options))
|
|
1001
|
-
}
|
|
1222
|
+
},
|
|
1223
|
+
...messageIdHeader ? { messageIdHeader } : {},
|
|
1224
|
+
...inReplyTo ? { inReplyTo } : {},
|
|
1225
|
+
...references.length > 0 ? { references } : {}
|
|
1002
1226
|
};
|
|
1003
1227
|
}
|
|
1004
1228
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ever-works/mailgun-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Mailgun email provider plugin (outbound + inbound) for Ever Works platform",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ever Co. LTD",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"test:watch": "vitest"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@ever-works/plugin": "^1.2.
|
|
35
|
+
"@ever-works/plugin": "^1.2.5",
|
|
36
36
|
"form-data": "^4.0.1",
|
|
37
37
|
"mailgun.js": "^10.2.3"
|
|
38
38
|
},
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@types/node": "^26.6.4",
|
|
41
41
|
"tsup": "^8.0.0",
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
|
-
"vitest": "^
|
|
43
|
+
"vitest": "^5.0.5"
|
|
44
44
|
},
|
|
45
45
|
"everworks": {
|
|
46
46
|
"plugin": {
|
|
@@ -102,8 +102,8 @@
|
|
|
102
102
|
"url": "https://github.com/ever-works/ever-works/issues"
|
|
103
103
|
},
|
|
104
104
|
"everworksRelease": {
|
|
105
|
-
"contentHash": "sha256:
|
|
106
|
-
"commit": "
|
|
107
|
-
"source": "https://github.com/ever-works/ever-works/tree/
|
|
105
|
+
"contentHash": "sha256:67b3989e2cd280ad55dd8d6de81ecf313d5dcfad08c9be4456db4bfa92cddb6f",
|
|
106
|
+
"commit": "19905c70cf05be3bc523db030597046067cd322a",
|
|
107
|
+
"source": "https://github.com/ever-works/ever-works/tree/19905c70cf05be3bc523db030597046067cd322a/packages/plugins/mailgun"
|
|
108
108
|
}
|
|
109
109
|
}
|