quidproquo-actionprocessor-awslambda 0.1.10 → 0.1.11
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/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.d.ts +2 -0
- package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js +43 -0
- package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js.map +1 -0
- package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.d.ts +2 -0
- package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js +26 -0
- package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js.map +1 -0
- package/lib/commonjs/getActionProcessor/webserver/email/index.d.ts +2 -0
- package/lib/commonjs/getActionProcessor/webserver/email/index.js +19 -0
- package/lib/commonjs/getActionProcessor/webserver/email/index.js.map +1 -0
- package/lib/commonjs/getActionProcessor/webserver/index.d.ts +1 -0
- package/lib/commonjs/getActionProcessor/webserver/index.js +3 -1
- package/lib/commonjs/getActionProcessor/webserver/index.js.map +1 -1
- package/lib/commonjs/logic/sesV2/buildRawMimeMessage.d.ts +2 -0
- package/lib/commonjs/logic/sesV2/buildRawMimeMessage.js +79 -0
- package/lib/commonjs/logic/sesV2/buildRawMimeMessage.js.map +1 -0
- package/lib/commonjs/logic/sesV2/sendEmail.d.ts +2 -0
- package/lib/commonjs/logic/sesV2/sendEmail.js +48 -0
- package/lib/commonjs/logic/sesV2/sendEmail.js.map +1 -0
- package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.d.ts +2 -0
- package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js +28 -0
- package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js.map +1 -0
- package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.d.ts +2 -0
- package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js +11 -0
- package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js.map +1 -0
- package/lib/esm/getActionProcessor/webserver/email/index.d.ts +2 -0
- package/lib/esm/getActionProcessor/webserver/email/index.js +7 -0
- package/lib/esm/getActionProcessor/webserver/email/index.js.map +1 -0
- package/lib/esm/getActionProcessor/webserver/index.d.ts +1 -0
- package/lib/esm/getActionProcessor/webserver/index.js +3 -0
- package/lib/esm/getActionProcessor/webserver/index.js.map +1 -1
- package/lib/esm/logic/sesV2/buildRawMimeMessage.d.ts +2 -0
- package/lib/esm/logic/sesV2/buildRawMimeMessage.js +74 -0
- package/lib/esm/logic/sesV2/buildRawMimeMessage.js.map +1 -0
- package/lib/esm/logic/sesV2/sendEmail.d.ts +2 -0
- package/lib/esm/logic/sesV2/sendEmail.js +37 -0
- package/lib/esm/logic/sesV2/sendEmail.js.map +1 -0
- package/lib/extension-layer/qpq-log-extension/index.js +22 -22
- package/package.json +25 -24
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getEmailSendEmailActionProcessor = void 0;
|
|
13
|
+
const quidproquo_config_aws_1 = require("quidproquo-config-aws");
|
|
14
|
+
const quidproquo_core_1 = require("quidproquo-core");
|
|
15
|
+
const quidproquo_webserver_1 = require("quidproquo-webserver");
|
|
16
|
+
const sendEmail_1 = require("../../../logic/sesV2/sendEmail");
|
|
17
|
+
const getProcessSendEmail = (qpqConfig) => {
|
|
18
|
+
return (payload) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
+
const region = quidproquo_config_aws_1.qpqConfigAwsUtils.getApplicationModuleDeployRegion(qpqConfig);
|
|
20
|
+
try {
|
|
21
|
+
const messageId = yield (0, sendEmail_1.sendEmail)(payload, region);
|
|
22
|
+
return (0, quidproquo_core_1.actionResult)(messageId);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return (0, quidproquo_core_1.actionResultErrorFromCaughtError)(error, {
|
|
26
|
+
MessageRejected: () => (0, quidproquo_core_1.actionResultError)(quidproquo_webserver_1.EmailSendEmailErrorTypeEnum.MessageRejected, 'Message rejected'),
|
|
27
|
+
MailFromDomainNotVerifiedException: () => (0, quidproquo_core_1.actionResultError)(quidproquo_webserver_1.EmailSendEmailErrorTypeEnum.SenderNotVerified, 'Sender domain not verified'),
|
|
28
|
+
AccountSuspendedException: () => (0, quidproquo_core_1.actionResultError)(quidproquo_webserver_1.EmailSendEmailErrorTypeEnum.AccountSuspended, 'Email sending account suspended'),
|
|
29
|
+
SendingPausedException: () => (0, quidproquo_core_1.actionResultError)(quidproquo_webserver_1.EmailSendEmailErrorTypeEnum.SendingPaused, 'Email sending is paused'),
|
|
30
|
+
TooManyRequestsException: () => (0, quidproquo_core_1.actionResultError)(quidproquo_webserver_1.EmailSendEmailErrorTypeEnum.Throttled, 'Rate exceeded'),
|
|
31
|
+
LimitExceededException: () => (0, quidproquo_core_1.actionResultError)(quidproquo_webserver_1.EmailSendEmailErrorTypeEnum.LimitExceeded, 'Sending limit exceeded'),
|
|
32
|
+
BadRequestException: () => (0, quidproquo_core_1.actionResultError)(quidproquo_webserver_1.EmailSendEmailErrorTypeEnum.BadRequest, 'Invalid email request'),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const getEmailSendEmailActionProcessor = (qpqConfig) => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
+
return ({
|
|
39
|
+
[quidproquo_webserver_1.EmailActionType.SendEmail]: getProcessSendEmail(qpqConfig),
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
exports.getEmailSendEmailActionProcessor = getEmailSendEmailActionProcessor;
|
|
43
|
+
//# sourceMappingURL=getEmailSendEmailActionProcessor.js.map
|
package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEmailSendEmailActionProcessor.js","sourceRoot":"","sources":["../../../../../src/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,iEAA0D;AAC1D,qDAOyB;AACzB,+DAAmH;AAEnH,8DAA2D;AAE3D,MAAM,mBAAmB,GAAG,CAAC,SAAoB,EAAiC,EAAE;IAClF,OAAO,CAAO,OAAO,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,yCAAiB,CAAC,gCAAgC,CAAC,SAAS,CAAC,CAAC;QAE7E,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,IAAA,qBAAS,EAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAEnD,OAAO,IAAA,8BAAY,EAAC,SAAS,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAA,kDAAgC,EAAC,KAAK,EAAE;gBAC7C,eAAe,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,kDAA2B,CAAC,eAAe,EAAE,kBAAkB,CAAC;gBACzG,kCAAkC,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,kDAA2B,CAAC,iBAAiB,EAAE,4BAA4B,CAAC;gBACxI,yBAAyB,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,kDAA2B,CAAC,gBAAgB,EAAE,iCAAiC,CAAC;gBACnI,sBAAsB,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,kDAA2B,CAAC,aAAa,EAAE,yBAAyB,CAAC;gBACrH,wBAAwB,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,kDAA2B,CAAC,SAAS,EAAE,eAAe,CAAC;gBACzG,sBAAsB,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,kDAA2B,CAAC,aAAa,EAAE,wBAAwB,CAAC;gBACpH,mBAAmB,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,kDAA2B,CAAC,UAAU,EAAE,uBAAuB,CAAC;aAC9G,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAA,CAAC;AACJ,CAAC,CAAC;AAEK,MAAM,gCAAgC,GAAgC,CAAO,SAAoB,EAAgC,EAAE;IAAC,OAAA,CAAC;QAC1I,CAAC,sCAAe,CAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC,SAAS,CAAC;KAC5D,CAAC,CAAA;EAAA,CAAC;AAFU,QAAA,gCAAgC,oCAE1C","sourcesContent":["import { qpqConfigAwsUtils } from 'quidproquo-config-aws';\nimport {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n actionResultErrorFromCaughtError,\n QPQConfig,\n} from 'quidproquo-core';\nimport { EmailActionType, EmailSendEmailActionProcessor, EmailSendEmailErrorTypeEnum } from 'quidproquo-webserver';\n\nimport { sendEmail } from '../../../logic/sesV2/sendEmail';\n\nconst getProcessSendEmail = (qpqConfig: QPQConfig): EmailSendEmailActionProcessor => {\n return async (payload) => {\n const region = qpqConfigAwsUtils.getApplicationModuleDeployRegion(qpqConfig);\n\n try {\n const messageId = await sendEmail(payload, region);\n\n return actionResult(messageId);\n } catch (error) {\n return actionResultErrorFromCaughtError(error, {\n MessageRejected: () => actionResultError(EmailSendEmailErrorTypeEnum.MessageRejected, 'Message rejected'),\n MailFromDomainNotVerifiedException: () => actionResultError(EmailSendEmailErrorTypeEnum.SenderNotVerified, 'Sender domain not verified'),\n AccountSuspendedException: () => actionResultError(EmailSendEmailErrorTypeEnum.AccountSuspended, 'Email sending account suspended'),\n SendingPausedException: () => actionResultError(EmailSendEmailErrorTypeEnum.SendingPaused, 'Email sending is paused'),\n TooManyRequestsException: () => actionResultError(EmailSendEmailErrorTypeEnum.Throttled, 'Rate exceeded'),\n LimitExceededException: () => actionResultError(EmailSendEmailErrorTypeEnum.LimitExceeded, 'Sending limit exceeded'),\n BadRequestException: () => actionResultError(EmailSendEmailErrorTypeEnum.BadRequest, 'Invalid email request'),\n });\n }\n };\n};\n\nexport const getEmailSendEmailActionProcessor: ActionProcessorListResolver = async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [EmailActionType.SendEmail]: getProcessSendEmail(qpqConfig),\n});\n"]}
|
package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getEmailSetDeliveryStatusActionProcessor = void 0;
|
|
13
|
+
const quidproquo_core_1 = require("quidproquo-core");
|
|
14
|
+
const quidproquo_webserver_1 = require("quidproquo-webserver");
|
|
15
|
+
// Deliberately a no-op: the action's meaning is its log entry, which the admin
|
|
16
|
+
// action search folds into the email's entity via the messageId link key.
|
|
17
|
+
const getProcessSetDeliveryStatus = () => {
|
|
18
|
+
return () => __awaiter(void 0, void 0, void 0, function* () { return (0, quidproquo_core_1.actionResult)(undefined); });
|
|
19
|
+
};
|
|
20
|
+
const getEmailSetDeliveryStatusActionProcessor = (qpqConfig) => __awaiter(void 0, void 0, void 0, function* () {
|
|
21
|
+
return ({
|
|
22
|
+
[quidproquo_webserver_1.EmailActionType.SetDeliveryStatus]: getProcessSetDeliveryStatus(),
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
exports.getEmailSetDeliveryStatusActionProcessor = getEmailSetDeliveryStatusActionProcessor;
|
|
26
|
+
//# sourceMappingURL=getEmailSetDeliveryStatusActionProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEmailSetDeliveryStatusActionProcessor.js","sourceRoot":"","sources":["../../../../../src/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA4G;AAC5G,+DAA8F;AAE9F,+EAA+E;AAC/E,0EAA0E;AAC1E,MAAM,2BAA2B,GAAG,GAA0C,EAAE;IAC9E,OAAO,GAAS,EAAE,kDAAC,OAAA,IAAA,8BAAY,EAAC,SAAS,CAAC,CAAA,GAAA,CAAC;AAC7C,CAAC,CAAC;AAEK,MAAM,wCAAwC,GAAgC,CAAO,SAAoB,EAAgC,EAAE;IAAC,OAAA,CAAC;QAClJ,CAAC,sCAAe,CAAC,iBAAiB,CAAC,EAAE,2BAA2B,EAAE;KACnE,CAAC,CAAA;EAAA,CAAC;AAFU,QAAA,wCAAwC,4CAElD","sourcesContent":["import { ActionProcessorList, ActionProcessorListResolver, actionResult, QPQConfig } from 'quidproquo-core';\nimport { EmailActionType, EmailSetDeliveryStatusActionProcessor } from 'quidproquo-webserver';\n\n// Deliberately a no-op: the action's meaning is its log entry, which the admin\n// action search folds into the email's entity via the messageId link key.\nconst getProcessSetDeliveryStatus = (): EmailSetDeliveryStatusActionProcessor => {\n return async () => actionResult(undefined);\n};\n\nexport const getEmailSetDeliveryStatusActionProcessor: ActionProcessorListResolver = async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [EmailActionType.SetDeliveryStatus]: getProcessSetDeliveryStatus(),\n});\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getEmailActionProcessor = void 0;
|
|
13
|
+
const getEmailSendEmailActionProcessor_1 = require("./getEmailSendEmailActionProcessor");
|
|
14
|
+
const getEmailSetDeliveryStatusActionProcessor_1 = require("./getEmailSetDeliveryStatusActionProcessor");
|
|
15
|
+
const getEmailActionProcessor = (qpqConfig, dynamicModuleLoader) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16
|
+
return (Object.assign(Object.assign({}, (yield (0, getEmailSendEmailActionProcessor_1.getEmailSendEmailActionProcessor)(qpqConfig, dynamicModuleLoader))), (yield (0, getEmailSetDeliveryStatusActionProcessor_1.getEmailSetDeliveryStatusActionProcessor)(qpqConfig, dynamicModuleLoader))));
|
|
17
|
+
});
|
|
18
|
+
exports.getEmailActionProcessor = getEmailActionProcessor;
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/getActionProcessor/webserver/email/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,yFAAsF;AACtF,yGAAsG;AAE/F,MAAM,uBAAuB,GAAgC,CAClE,SAAoB,EACpB,mBAAwC,EACV,EAAE;IAAC,OAAA,iCAC9B,CAAC,MAAM,IAAA,mEAAgC,EAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC,GACxE,CAAC,MAAM,IAAA,mFAAwC,EAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC,EACnF,CAAA;EAAA,CAAC;AANU,QAAA,uBAAuB,2BAMjC","sourcesContent":["import { ActionProcessorList, ActionProcessorListResolver, DynamicModuleLoader, QPQConfig } from 'quidproquo-core';\n\nimport { getEmailSendEmailActionProcessor } from './getEmailSendEmailActionProcessor';\nimport { getEmailSetDeliveryStatusActionProcessor } from './getEmailSetDeliveryStatusActionProcessor';\n\nexport const getEmailActionProcessor: ActionProcessorListResolver = async (\n qpqConfig: QPQConfig,\n dynamicModuleLoader: DynamicModuleLoader,\n): Promise<ActionProcessorList> => ({\n ...(await getEmailSendEmailActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getEmailSetDeliveryStatusActionProcessor(qpqConfig, dynamicModuleLoader)),\n});\n"]}
|
|
@@ -24,14 +24,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.getWebserverActionProcessor = void 0;
|
|
27
|
+
const email_1 = require("./email");
|
|
27
28
|
const serviceFunction_1 = require("./serviceFunction");
|
|
28
29
|
const webEntry_1 = require("./webEntry");
|
|
29
30
|
const websocket_1 = require("./websocket");
|
|
31
|
+
__exportStar(require("./email"), exports);
|
|
30
32
|
__exportStar(require("./serviceFunction"), exports);
|
|
31
33
|
__exportStar(require("./webEntry"), exports);
|
|
32
34
|
__exportStar(require("./websocket"), exports);
|
|
33
35
|
const getWebserverActionProcessor = (qpqConfig, dynamicModuleLoader) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
-
return (Object.assign(Object.assign(Object.assign({}, (yield (0, webEntry_1.getWebEntryActionProcessor)(qpqConfig, dynamicModuleLoader))), (yield (0, serviceFunction_1.getServiceFunctionActionProcessor)(qpqConfig, dynamicModuleLoader))), (yield (0, websocket_1.getWebsocketActionProcessor)(qpqConfig, dynamicModuleLoader))));
|
|
36
|
+
return (Object.assign(Object.assign(Object.assign(Object.assign({}, (yield (0, email_1.getEmailActionProcessor)(qpqConfig, dynamicModuleLoader))), (yield (0, webEntry_1.getWebEntryActionProcessor)(qpqConfig, dynamicModuleLoader))), (yield (0, serviceFunction_1.getServiceFunctionActionProcessor)(qpqConfig, dynamicModuleLoader))), (yield (0, websocket_1.getWebsocketActionProcessor)(qpqConfig, dynamicModuleLoader))));
|
|
35
37
|
});
|
|
36
38
|
exports.getWebserverActionProcessor = getWebserverActionProcessor;
|
|
37
39
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/getActionProcessor/webserver/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uDAAsE;AACtE,yCAAwD;AACxD,2CAA0D;AAE1D,oDAAkC;AAClC,6CAA2B;AAC3B,8CAA4B;AAIrB,MAAM,2BAA2B,GAAgC,CACtE,SAAoB,EACpB,mBAAwC,EACV,EAAE;IAAC,OAAA,+
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/getActionProcessor/webserver/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mCAAkD;AAClD,uDAAsE;AACtE,yCAAwD;AACxD,2CAA0D;AAE1D,0CAAwB;AACxB,oDAAkC;AAClC,6CAA2B;AAC3B,8CAA4B;AAIrB,MAAM,2BAA2B,GAAgC,CACtE,SAAoB,EACpB,mBAAwC,EACV,EAAE;IAAC,OAAA,6DAC9B,CAAC,MAAM,IAAA,+BAAuB,EAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC,GAC/D,CAAC,MAAM,IAAA,qCAA0B,EAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC,GAClE,CAAC,MAAM,IAAA,mDAAiC,EAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC,GACzE,CAAC,MAAM,IAAA,uCAA2B,EAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC,EACtE,CAAA;EAAA,CAAC;AARU,QAAA,2BAA2B,+BAQrC","sourcesContent":["import { getEmailActionProcessor } from './email';\nimport { getServiceFunctionActionProcessor } from './serviceFunction';\nimport { getWebEntryActionProcessor } from './webEntry';\nimport { getWebsocketActionProcessor } from './websocket';\n\nexport * from './email';\nexport * from './serviceFunction';\nexport * from './webEntry';\nexport * from './websocket';\n\nimport { ActionProcessorList, ActionProcessorListResolver, DynamicModuleLoader, QPQConfig } from 'quidproquo-core';\n\nexport const getWebserverActionProcessor: ActionProcessorListResolver = async (\n qpqConfig: QPQConfig,\n dynamicModuleLoader: DynamicModuleLoader,\n): Promise<ActionProcessorList> => ({\n ...(await getEmailActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getWebEntryActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getServiceFunctionActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getWebsocketActionProcessor(qpqConfig, dynamicModuleLoader)),\n});\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildRawMimeMessage = void 0;
|
|
4
|
+
const CRLF = '\r\n';
|
|
5
|
+
// Header values must never contain CR/LF, or a caller-supplied string could inject extra headers
|
|
6
|
+
const sanitizeHeaderValue = (value) => value.replace(/[\r\n]+/g, ' ').trim();
|
|
7
|
+
// RFC 2047 encoded-word for header text that isn't printable ascii (e.g. subjects with emoji)
|
|
8
|
+
const encodeHeaderText = (value) => {
|
|
9
|
+
const sanitized = sanitizeHeaderValue(value);
|
|
10
|
+
if (/^[\x20-\x7e]*$/.test(sanitized)) {
|
|
11
|
+
return sanitized;
|
|
12
|
+
}
|
|
13
|
+
return `=?UTF-8?B?${Buffer.from(sanitized, 'utf8').toString('base64')}?=`;
|
|
14
|
+
};
|
|
15
|
+
// RFC 2045 requires base64 body lines of at most 76 characters
|
|
16
|
+
const wrapBase64 = (base64Data) => {
|
|
17
|
+
const lines = base64Data.match(/.{1,76}/g) || [];
|
|
18
|
+
return lines.join(CRLF);
|
|
19
|
+
};
|
|
20
|
+
const buildBodyPart = (contentType, content, boundary) => [
|
|
21
|
+
`--${boundary}`,
|
|
22
|
+
`Content-Type: ${contentType}; charset=UTF-8`,
|
|
23
|
+
'Content-Transfer-Encoding: base64',
|
|
24
|
+
'',
|
|
25
|
+
wrapBase64(Buffer.from(content, 'utf8').toString('base64')),
|
|
26
|
+
];
|
|
27
|
+
const buildAttachmentPart = (attachment, boundary) => {
|
|
28
|
+
const filename = encodeHeaderText(attachment.filename);
|
|
29
|
+
const contentDisposition = attachment.contentDisposition
|
|
30
|
+
? sanitizeHeaderValue(attachment.contentDisposition)
|
|
31
|
+
: `attachment; filename="${filename}"`;
|
|
32
|
+
return [
|
|
33
|
+
`--${boundary}`,
|
|
34
|
+
`Content-Type: ${sanitizeHeaderValue(attachment.mimetype || 'application/octet-stream')}; name="${filename}"`,
|
|
35
|
+
'Content-Transfer-Encoding: base64',
|
|
36
|
+
`Content-Disposition: ${contentDisposition}`,
|
|
37
|
+
'',
|
|
38
|
+
wrapBase64(attachment.base64Data),
|
|
39
|
+
];
|
|
40
|
+
};
|
|
41
|
+
// Builds an RFC 5322 message: multipart/mixed wrapping a multipart/alternative body plus one
|
|
42
|
+
// part per attachment. Bcc recipients are deliberately never written into the headers; they
|
|
43
|
+
// travel only in the SES Destination so other recipients can't see them. boundarySeed exists
|
|
44
|
+
// so tests get deterministic output.
|
|
45
|
+
const buildRawMimeMessage = (payload, boundarySeed = 'qpq') => {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
const mixedBoundary = `----=_${boundarySeed}_mixed`;
|
|
48
|
+
const alternativeBoundary = `----=_${boundarySeed}_alternative`;
|
|
49
|
+
const headers = [
|
|
50
|
+
`From: ${encodeHeaderText(payload.from)}`,
|
|
51
|
+
`To: ${payload.to.map(encodeHeaderText).join(', ')}`,
|
|
52
|
+
...(((_a = payload.cc) === null || _a === void 0 ? void 0 : _a.length) ? [`Cc: ${payload.cc.map(encodeHeaderText).join(', ')}`] : []),
|
|
53
|
+
...(((_b = payload.replyTo) === null || _b === void 0 ? void 0 : _b.length) ? [`Reply-To: ${payload.replyTo.map(encodeHeaderText).join(', ')}`] : []),
|
|
54
|
+
`Subject: ${encodeHeaderText(payload.subject)}`,
|
|
55
|
+
'MIME-Version: 1.0',
|
|
56
|
+
`Content-Type: multipart/mixed; boundary="${mixedBoundary}"`,
|
|
57
|
+
];
|
|
58
|
+
const bodyParts = [
|
|
59
|
+
...(payload.bodyText ? buildBodyPart('text/plain', payload.bodyText, alternativeBoundary) : []),
|
|
60
|
+
...(payload.bodyHtml ? buildBodyPart('text/html', payload.bodyHtml, alternativeBoundary) : []),
|
|
61
|
+
];
|
|
62
|
+
const attachmentParts = (payload.attachments || []).flatMap((attachment) => buildAttachmentPart(attachment, mixedBoundary));
|
|
63
|
+
const lines = [
|
|
64
|
+
...headers,
|
|
65
|
+
'',
|
|
66
|
+
`--${mixedBoundary}`,
|
|
67
|
+
`Content-Type: multipart/alternative; boundary="${alternativeBoundary}"`,
|
|
68
|
+
'',
|
|
69
|
+
...bodyParts,
|
|
70
|
+
`--${alternativeBoundary}--`,
|
|
71
|
+
'',
|
|
72
|
+
...attachmentParts,
|
|
73
|
+
`--${mixedBoundary}--`,
|
|
74
|
+
'',
|
|
75
|
+
];
|
|
76
|
+
return lines.join(CRLF);
|
|
77
|
+
};
|
|
78
|
+
exports.buildRawMimeMessage = buildRawMimeMessage;
|
|
79
|
+
//# sourceMappingURL=buildRawMimeMessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildRawMimeMessage.js","sourceRoot":"","sources":["../../../../src/logic/sesV2/buildRawMimeMessage.ts"],"names":[],"mappings":"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","sourcesContent":["import { QPQBinaryData } from 'quidproquo-core';\nimport { EmailSendEmailActionPayload } from 'quidproquo-webserver';\n\nconst CRLF = '\\r\\n';\n\n// Header values must never contain CR/LF, or a caller-supplied string could inject extra headers\nconst sanitizeHeaderValue = (value: string): string => value.replace(/[\\r\\n]+/g, ' ').trim();\n\n// RFC 2047 encoded-word for header text that isn't printable ascii (e.g. subjects with emoji)\nconst encodeHeaderText = (value: string): string => {\n const sanitized = sanitizeHeaderValue(value);\n\n if (/^[\\x20-\\x7e]*$/.test(sanitized)) {\n return sanitized;\n }\n\n return `=?UTF-8?B?${Buffer.from(sanitized, 'utf8').toString('base64')}?=`;\n};\n\n// RFC 2045 requires base64 body lines of at most 76 characters\nconst wrapBase64 = (base64Data: string): string => {\n const lines = base64Data.match(/.{1,76}/g) || [];\n\n return lines.join(CRLF);\n};\n\nconst buildBodyPart = (contentType: string, content: string, boundary: string): string[] => [\n `--${boundary}`,\n `Content-Type: ${contentType}; charset=UTF-8`,\n 'Content-Transfer-Encoding: base64',\n '',\n wrapBase64(Buffer.from(content, 'utf8').toString('base64')),\n];\n\nconst buildAttachmentPart = (attachment: QPQBinaryData, boundary: string): string[] => {\n const filename = encodeHeaderText(attachment.filename);\n const contentDisposition = attachment.contentDisposition\n ? sanitizeHeaderValue(attachment.contentDisposition)\n : `attachment; filename=\"${filename}\"`;\n\n return [\n `--${boundary}`,\n `Content-Type: ${sanitizeHeaderValue(attachment.mimetype || 'application/octet-stream')}; name=\"${filename}\"`,\n 'Content-Transfer-Encoding: base64',\n `Content-Disposition: ${contentDisposition}`,\n '',\n wrapBase64(attachment.base64Data),\n ];\n};\n\n// Builds an RFC 5322 message: multipart/mixed wrapping a multipart/alternative body plus one\n// part per attachment. Bcc recipients are deliberately never written into the headers; they\n// travel only in the SES Destination so other recipients can't see them. boundarySeed exists\n// so tests get deterministic output.\nexport const buildRawMimeMessage = (payload: EmailSendEmailActionPayload, boundarySeed: string = 'qpq'): string => {\n const mixedBoundary = `----=_${boundarySeed}_mixed`;\n const alternativeBoundary = `----=_${boundarySeed}_alternative`;\n\n const headers = [\n `From: ${encodeHeaderText(payload.from)}`,\n `To: ${payload.to.map(encodeHeaderText).join(', ')}`,\n ...(payload.cc?.length ? [`Cc: ${payload.cc.map(encodeHeaderText).join(', ')}`] : []),\n ...(payload.replyTo?.length ? [`Reply-To: ${payload.replyTo.map(encodeHeaderText).join(', ')}`] : []),\n `Subject: ${encodeHeaderText(payload.subject)}`,\n 'MIME-Version: 1.0',\n `Content-Type: multipart/mixed; boundary=\"${mixedBoundary}\"`,\n ];\n\n const bodyParts = [\n ...(payload.bodyText ? buildBodyPart('text/plain', payload.bodyText, alternativeBoundary) : []),\n ...(payload.bodyHtml ? buildBodyPart('text/html', payload.bodyHtml, alternativeBoundary) : []),\n ];\n\n const attachmentParts = (payload.attachments || []).flatMap((attachment) => buildAttachmentPart(attachment, mixedBoundary));\n\n const lines = [\n ...headers,\n '',\n `--${mixedBoundary}`,\n `Content-Type: multipart/alternative; boundary=\"${alternativeBoundary}\"`,\n '',\n ...bodyParts,\n `--${alternativeBoundary}--`,\n '',\n ...attachmentParts,\n `--${mixedBoundary}--`,\n '',\n ];\n\n return lines.join(CRLF);\n};\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.sendEmail = void 0;
|
|
13
|
+
const client_sesv2_1 = require("@aws-sdk/client-sesv2");
|
|
14
|
+
const createAwsClient_1 = require("../createAwsClient");
|
|
15
|
+
const buildRawMimeMessage_1 = require("./buildRawMimeMessage");
|
|
16
|
+
// SES Simple content can't carry attachments, so those emails go through the raw MIME path.
|
|
17
|
+
// Either way the recipients come from Destination below, which is what keeps bcc addresses
|
|
18
|
+
// out of the message the recipients see.
|
|
19
|
+
const getEmailContent = (payload) => {
|
|
20
|
+
var _a;
|
|
21
|
+
if ((_a = payload.attachments) === null || _a === void 0 ? void 0 : _a.length) {
|
|
22
|
+
return {
|
|
23
|
+
Raw: { Data: new TextEncoder().encode((0, buildRawMimeMessage_1.buildRawMimeMessage)(payload)) },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
Simple: {
|
|
28
|
+
Subject: { Data: payload.subject, Charset: 'UTF-8' },
|
|
29
|
+
Body: Object.assign(Object.assign({}, (payload.bodyText ? { Text: { Data: payload.bodyText, Charset: 'UTF-8' } } : {})), (payload.bodyHtml ? { Html: { Data: payload.bodyHtml, Charset: 'UTF-8' } } : {})),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const sendEmail = (payload, region) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
const sesClient = (0, createAwsClient_1.createAwsClient)(client_sesv2_1.SESv2Client, { region });
|
|
35
|
+
const response = yield sesClient.send(new client_sesv2_1.SendEmailCommand({
|
|
36
|
+
FromEmailAddress: payload.from,
|
|
37
|
+
Destination: {
|
|
38
|
+
ToAddresses: payload.to,
|
|
39
|
+
CcAddresses: payload.cc,
|
|
40
|
+
BccAddresses: payload.bcc,
|
|
41
|
+
},
|
|
42
|
+
ReplyToAddresses: payload.replyTo,
|
|
43
|
+
Content: getEmailContent(payload),
|
|
44
|
+
}));
|
|
45
|
+
return response.MessageId || '';
|
|
46
|
+
});
|
|
47
|
+
exports.sendEmail = sendEmail;
|
|
48
|
+
//# sourceMappingURL=sendEmail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sendEmail.js","sourceRoot":"","sources":["../../../../src/logic/sesV2/sendEmail.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,wDAAoF;AAEpF,wDAAqD;AACrD,+DAA4D;AAE5D,4FAA4F;AAC5F,2FAA2F;AAC3F,yCAAyC;AACzC,MAAM,eAAe,GAAG,CAAC,OAAoC,EAAgB,EAAE;;IAC7E,IAAI,MAAA,OAAO,CAAC,WAAW,0CAAE,MAAM,EAAE,CAAC;QAChC,OAAO;YACL,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAA,yCAAmB,EAAC,OAAO,CAAC,CAAC,EAAE;SACtE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE;YACN,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;YACpD,IAAI,kCACC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAChF,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEK,MAAM,SAAS,GAAG,CAAO,OAAoC,EAAE,MAAc,EAAmB,EAAE;IACvG,MAAM,SAAS,GAAG,IAAA,iCAAe,EAAC,0BAAW,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAE3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,IAAI,CACnC,IAAI,+BAAgB,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC,IAAI;QAC9B,WAAW,EAAE;YACX,WAAW,EAAE,OAAO,CAAC,EAAE;YACvB,WAAW,EAAE,OAAO,CAAC,EAAE;YACvB,YAAY,EAAE,OAAO,CAAC,GAAG;SAC1B;QACD,gBAAgB,EAAE,OAAO,CAAC,OAAO;QACjC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC;KAClC,CAAC,CACH,CAAC;IAEF,OAAO,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC;AAClC,CAAC,CAAA,CAAC;AAjBW,QAAA,SAAS,aAiBpB","sourcesContent":["import { EmailSendEmailActionPayload } from 'quidproquo-webserver';\n\nimport { EmailContent, SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';\n\nimport { createAwsClient } from '../createAwsClient';\nimport { buildRawMimeMessage } from './buildRawMimeMessage';\n\n// SES Simple content can't carry attachments, so those emails go through the raw MIME path.\n// Either way the recipients come from Destination below, which is what keeps bcc addresses\n// out of the message the recipients see.\nconst getEmailContent = (payload: EmailSendEmailActionPayload): EmailContent => {\n if (payload.attachments?.length) {\n return {\n Raw: { Data: new TextEncoder().encode(buildRawMimeMessage(payload)) },\n };\n }\n\n return {\n Simple: {\n Subject: { Data: payload.subject, Charset: 'UTF-8' },\n Body: {\n ...(payload.bodyText ? { Text: { Data: payload.bodyText, Charset: 'UTF-8' } } : {}),\n ...(payload.bodyHtml ? { Html: { Data: payload.bodyHtml, Charset: 'UTF-8' } } : {}),\n },\n },\n };\n};\n\nexport const sendEmail = async (payload: EmailSendEmailActionPayload, region: string): Promise<string> => {\n const sesClient = createAwsClient(SESv2Client, { region });\n\n const response = await sesClient.send(\n new SendEmailCommand({\n FromEmailAddress: payload.from,\n Destination: {\n ToAddresses: payload.to,\n CcAddresses: payload.cc,\n BccAddresses: payload.bcc,\n },\n ReplyToAddresses: payload.replyTo,\n Content: getEmailContent(payload),\n }),\n );\n\n return response.MessageId || '';\n};\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
|
|
2
|
+
import { actionResult, actionResultError, actionResultErrorFromCaughtError, } from 'quidproquo-core';
|
|
3
|
+
import { EmailActionType, EmailSendEmailErrorTypeEnum } from 'quidproquo-webserver';
|
|
4
|
+
import { sendEmail } from '../../../logic/sesV2/sendEmail';
|
|
5
|
+
const getProcessSendEmail = (qpqConfig) => {
|
|
6
|
+
return async (payload) => {
|
|
7
|
+
const region = qpqConfigAwsUtils.getApplicationModuleDeployRegion(qpqConfig);
|
|
8
|
+
try {
|
|
9
|
+
const messageId = await sendEmail(payload, region);
|
|
10
|
+
return actionResult(messageId);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return actionResultErrorFromCaughtError(error, {
|
|
14
|
+
MessageRejected: () => actionResultError(EmailSendEmailErrorTypeEnum.MessageRejected, 'Message rejected'),
|
|
15
|
+
MailFromDomainNotVerifiedException: () => actionResultError(EmailSendEmailErrorTypeEnum.SenderNotVerified, 'Sender domain not verified'),
|
|
16
|
+
AccountSuspendedException: () => actionResultError(EmailSendEmailErrorTypeEnum.AccountSuspended, 'Email sending account suspended'),
|
|
17
|
+
SendingPausedException: () => actionResultError(EmailSendEmailErrorTypeEnum.SendingPaused, 'Email sending is paused'),
|
|
18
|
+
TooManyRequestsException: () => actionResultError(EmailSendEmailErrorTypeEnum.Throttled, 'Rate exceeded'),
|
|
19
|
+
LimitExceededException: () => actionResultError(EmailSendEmailErrorTypeEnum.LimitExceeded, 'Sending limit exceeded'),
|
|
20
|
+
BadRequestException: () => actionResultError(EmailSendEmailErrorTypeEnum.BadRequest, 'Invalid email request'),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export const getEmailSendEmailActionProcessor = async (qpqConfig) => ({
|
|
26
|
+
[EmailActionType.SendEmail]: getProcessSendEmail(qpqConfig),
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=getEmailSendEmailActionProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEmailSendEmailActionProcessor.js","sourceRoot":"","sources":["../../../../../src/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,gCAAgC,GAEjC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAiC,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AAEnH,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAE3D,MAAM,mBAAmB,GAAG,CAAC,SAAoB,EAAiC,EAAE;IAClF,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,iBAAiB,CAAC,gCAAgC,CAAC,SAAS,CAAC,CAAC;QAE7E,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAEnD,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,gCAAgC,CAAC,KAAK,EAAE;gBAC7C,eAAe,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,2BAA2B,CAAC,eAAe,EAAE,kBAAkB,CAAC;gBACzG,kCAAkC,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,2BAA2B,CAAC,iBAAiB,EAAE,4BAA4B,CAAC;gBACxI,yBAAyB,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,2BAA2B,CAAC,gBAAgB,EAAE,iCAAiC,CAAC;gBACnI,sBAAsB,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,2BAA2B,CAAC,aAAa,EAAE,yBAAyB,CAAC;gBACrH,wBAAwB,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,2BAA2B,CAAC,SAAS,EAAE,eAAe,CAAC;gBACzG,sBAAsB,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,2BAA2B,CAAC,aAAa,EAAE,wBAAwB,CAAC;gBACpH,mBAAmB,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,2BAA2B,CAAC,UAAU,EAAE,uBAAuB,CAAC;aAC9G,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gCAAgC,GAAgC,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IAC1I,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC,SAAS,CAAC;CAC5D,CAAC,CAAC","sourcesContent":["import { qpqConfigAwsUtils } from 'quidproquo-config-aws';\nimport {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n actionResultErrorFromCaughtError,\n QPQConfig,\n} from 'quidproquo-core';\nimport { EmailActionType, EmailSendEmailActionProcessor, EmailSendEmailErrorTypeEnum } from 'quidproquo-webserver';\n\nimport { sendEmail } from '../../../logic/sesV2/sendEmail';\n\nconst getProcessSendEmail = (qpqConfig: QPQConfig): EmailSendEmailActionProcessor => {\n return async (payload) => {\n const region = qpqConfigAwsUtils.getApplicationModuleDeployRegion(qpqConfig);\n\n try {\n const messageId = await sendEmail(payload, region);\n\n return actionResult(messageId);\n } catch (error) {\n return actionResultErrorFromCaughtError(error, {\n MessageRejected: () => actionResultError(EmailSendEmailErrorTypeEnum.MessageRejected, 'Message rejected'),\n MailFromDomainNotVerifiedException: () => actionResultError(EmailSendEmailErrorTypeEnum.SenderNotVerified, 'Sender domain not verified'),\n AccountSuspendedException: () => actionResultError(EmailSendEmailErrorTypeEnum.AccountSuspended, 'Email sending account suspended'),\n SendingPausedException: () => actionResultError(EmailSendEmailErrorTypeEnum.SendingPaused, 'Email sending is paused'),\n TooManyRequestsException: () => actionResultError(EmailSendEmailErrorTypeEnum.Throttled, 'Rate exceeded'),\n LimitExceededException: () => actionResultError(EmailSendEmailErrorTypeEnum.LimitExceeded, 'Sending limit exceeded'),\n BadRequestException: () => actionResultError(EmailSendEmailErrorTypeEnum.BadRequest, 'Invalid email request'),\n });\n }\n };\n};\n\nexport const getEmailSendEmailActionProcessor: ActionProcessorListResolver = async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [EmailActionType.SendEmail]: getProcessSendEmail(qpqConfig),\n});\n"]}
|
package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { actionResult } from 'quidproquo-core';
|
|
2
|
+
import { EmailActionType } from 'quidproquo-webserver';
|
|
3
|
+
// Deliberately a no-op: the action's meaning is its log entry, which the admin
|
|
4
|
+
// action search folds into the email's entity via the messageId link key.
|
|
5
|
+
const getProcessSetDeliveryStatus = () => {
|
|
6
|
+
return async () => actionResult(undefined);
|
|
7
|
+
};
|
|
8
|
+
export const getEmailSetDeliveryStatusActionProcessor = async (qpqConfig) => ({
|
|
9
|
+
[EmailActionType.SetDeliveryStatus]: getProcessSetDeliveryStatus(),
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=getEmailSetDeliveryStatusActionProcessor.js.map
|
package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEmailSetDeliveryStatusActionProcessor.js","sourceRoot":"","sources":["../../../../../src/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoD,YAAY,EAAa,MAAM,iBAAiB,CAAC;AAC5G,OAAO,EAAE,eAAe,EAAyC,MAAM,sBAAsB,CAAC;AAE9F,+EAA+E;AAC/E,0EAA0E;AAC1E,MAAM,2BAA2B,GAAG,GAA0C,EAAE;IAC9E,OAAO,KAAK,IAAI,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;AAC7C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,wCAAwC,GAAgC,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IAClJ,CAAC,eAAe,CAAC,iBAAiB,CAAC,EAAE,2BAA2B,EAAE;CACnE,CAAC,CAAC","sourcesContent":["import { ActionProcessorList, ActionProcessorListResolver, actionResult, QPQConfig } from 'quidproquo-core';\nimport { EmailActionType, EmailSetDeliveryStatusActionProcessor } from 'quidproquo-webserver';\n\n// Deliberately a no-op: the action's meaning is its log entry, which the admin\n// action search folds into the email's entity via the messageId link key.\nconst getProcessSetDeliveryStatus = (): EmailSetDeliveryStatusActionProcessor => {\n return async () => actionResult(undefined);\n};\n\nexport const getEmailSetDeliveryStatusActionProcessor: ActionProcessorListResolver = async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [EmailActionType.SetDeliveryStatus]: getProcessSetDeliveryStatus(),\n});\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getEmailSendEmailActionProcessor } from './getEmailSendEmailActionProcessor';
|
|
2
|
+
import { getEmailSetDeliveryStatusActionProcessor } from './getEmailSetDeliveryStatusActionProcessor';
|
|
3
|
+
export const getEmailActionProcessor = async (qpqConfig, dynamicModuleLoader) => ({
|
|
4
|
+
...(await getEmailSendEmailActionProcessor(qpqConfig, dynamicModuleLoader)),
|
|
5
|
+
...(await getEmailSetDeliveryStatusActionProcessor(qpqConfig, dynamicModuleLoader)),
|
|
6
|
+
});
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/getActionProcessor/webserver/email/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,EAAE,wCAAwC,EAAE,MAAM,4CAA4C,CAAC;AAEtG,MAAM,CAAC,MAAM,uBAAuB,GAAgC,KAAK,EACvE,SAAoB,EACpB,mBAAwC,EACV,EAAE,CAAC,CAAC;IAClC,GAAG,CAAC,MAAM,gCAAgC,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAC3E,GAAG,CAAC,MAAM,wCAAwC,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;CACpF,CAAC,CAAC","sourcesContent":["import { ActionProcessorList, ActionProcessorListResolver, DynamicModuleLoader, QPQConfig } from 'quidproquo-core';\n\nimport { getEmailSendEmailActionProcessor } from './getEmailSendEmailActionProcessor';\nimport { getEmailSetDeliveryStatusActionProcessor } from './getEmailSetDeliveryStatusActionProcessor';\n\nexport const getEmailActionProcessor: ActionProcessorListResolver = async (\n qpqConfig: QPQConfig,\n dynamicModuleLoader: DynamicModuleLoader,\n): Promise<ActionProcessorList> => ({\n ...(await getEmailSendEmailActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getEmailSetDeliveryStatusActionProcessor(qpqConfig, dynamicModuleLoader)),\n});\n"]}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { getEmailActionProcessor } from './email';
|
|
1
2
|
import { getServiceFunctionActionProcessor } from './serviceFunction';
|
|
2
3
|
import { getWebEntryActionProcessor } from './webEntry';
|
|
3
4
|
import { getWebsocketActionProcessor } from './websocket';
|
|
5
|
+
export * from './email';
|
|
4
6
|
export * from './serviceFunction';
|
|
5
7
|
export * from './webEntry';
|
|
6
8
|
export * from './websocket';
|
|
7
9
|
export const getWebserverActionProcessor = async (qpqConfig, dynamicModuleLoader) => ({
|
|
10
|
+
...(await getEmailActionProcessor(qpqConfig, dynamicModuleLoader)),
|
|
8
11
|
...(await getWebEntryActionProcessor(qpqConfig, dynamicModuleLoader)),
|
|
9
12
|
...(await getServiceFunctionActionProcessor(qpqConfig, dynamicModuleLoader)),
|
|
10
13
|
...(await getWebsocketActionProcessor(qpqConfig, dynamicModuleLoader)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/getActionProcessor/webserver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iCAAiC,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAI5B,MAAM,CAAC,MAAM,2BAA2B,GAAgC,KAAK,EAC3E,SAAoB,EACpB,mBAAwC,EACV,EAAE,CAAC,CAAC;IAClC,GAAG,CAAC,MAAM,0BAA0B,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IACrE,GAAG,CAAC,MAAM,iCAAiC,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAC5E,GAAG,CAAC,MAAM,2BAA2B,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;CACvE,CAAC,CAAC","sourcesContent":["import { getServiceFunctionActionProcessor } from './serviceFunction';\nimport { getWebEntryActionProcessor } from './webEntry';\nimport { getWebsocketActionProcessor } from './websocket';\n\nexport * from './serviceFunction';\nexport * from './webEntry';\nexport * from './websocket';\n\nimport { ActionProcessorList, ActionProcessorListResolver, DynamicModuleLoader, QPQConfig } from 'quidproquo-core';\n\nexport const getWebserverActionProcessor: ActionProcessorListResolver = async (\n qpqConfig: QPQConfig,\n dynamicModuleLoader: DynamicModuleLoader,\n): Promise<ActionProcessorList> => ({\n ...(await getWebEntryActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getServiceFunctionActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getWebsocketActionProcessor(qpqConfig, dynamicModuleLoader)),\n});\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/getActionProcessor/webserver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,iCAAiC,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAI5B,MAAM,CAAC,MAAM,2BAA2B,GAAgC,KAAK,EAC3E,SAAoB,EACpB,mBAAwC,EACV,EAAE,CAAC,CAAC;IAClC,GAAG,CAAC,MAAM,uBAAuB,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAClE,GAAG,CAAC,MAAM,0BAA0B,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IACrE,GAAG,CAAC,MAAM,iCAAiC,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAC5E,GAAG,CAAC,MAAM,2BAA2B,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;CACvE,CAAC,CAAC","sourcesContent":["import { getEmailActionProcessor } from './email';\nimport { getServiceFunctionActionProcessor } from './serviceFunction';\nimport { getWebEntryActionProcessor } from './webEntry';\nimport { getWebsocketActionProcessor } from './websocket';\n\nexport * from './email';\nexport * from './serviceFunction';\nexport * from './webEntry';\nexport * from './websocket';\n\nimport { ActionProcessorList, ActionProcessorListResolver, DynamicModuleLoader, QPQConfig } from 'quidproquo-core';\n\nexport const getWebserverActionProcessor: ActionProcessorListResolver = async (\n qpqConfig: QPQConfig,\n dynamicModuleLoader: DynamicModuleLoader,\n): Promise<ActionProcessorList> => ({\n ...(await getEmailActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getWebEntryActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getServiceFunctionActionProcessor(qpqConfig, dynamicModuleLoader)),\n ...(await getWebsocketActionProcessor(qpqConfig, dynamicModuleLoader)),\n});\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const CRLF = '\r\n';
|
|
2
|
+
// Header values must never contain CR/LF, or a caller-supplied string could inject extra headers
|
|
3
|
+
const sanitizeHeaderValue = (value) => value.replace(/[\r\n]+/g, ' ').trim();
|
|
4
|
+
// RFC 2047 encoded-word for header text that isn't printable ascii (e.g. subjects with emoji)
|
|
5
|
+
const encodeHeaderText = (value) => {
|
|
6
|
+
const sanitized = sanitizeHeaderValue(value);
|
|
7
|
+
if (/^[\x20-\x7e]*$/.test(sanitized)) {
|
|
8
|
+
return sanitized;
|
|
9
|
+
}
|
|
10
|
+
return `=?UTF-8?B?${Buffer.from(sanitized, 'utf8').toString('base64')}?=`;
|
|
11
|
+
};
|
|
12
|
+
// RFC 2045 requires base64 body lines of at most 76 characters
|
|
13
|
+
const wrapBase64 = (base64Data) => {
|
|
14
|
+
const lines = base64Data.match(/.{1,76}/g) || [];
|
|
15
|
+
return lines.join(CRLF);
|
|
16
|
+
};
|
|
17
|
+
const buildBodyPart = (contentType, content, boundary) => [
|
|
18
|
+
`--${boundary}`,
|
|
19
|
+
`Content-Type: ${contentType}; charset=UTF-8`,
|
|
20
|
+
'Content-Transfer-Encoding: base64',
|
|
21
|
+
'',
|
|
22
|
+
wrapBase64(Buffer.from(content, 'utf8').toString('base64')),
|
|
23
|
+
];
|
|
24
|
+
const buildAttachmentPart = (attachment, boundary) => {
|
|
25
|
+
const filename = encodeHeaderText(attachment.filename);
|
|
26
|
+
const contentDisposition = attachment.contentDisposition
|
|
27
|
+
? sanitizeHeaderValue(attachment.contentDisposition)
|
|
28
|
+
: `attachment; filename="${filename}"`;
|
|
29
|
+
return [
|
|
30
|
+
`--${boundary}`,
|
|
31
|
+
`Content-Type: ${sanitizeHeaderValue(attachment.mimetype || 'application/octet-stream')}; name="${filename}"`,
|
|
32
|
+
'Content-Transfer-Encoding: base64',
|
|
33
|
+
`Content-Disposition: ${contentDisposition}`,
|
|
34
|
+
'',
|
|
35
|
+
wrapBase64(attachment.base64Data),
|
|
36
|
+
];
|
|
37
|
+
};
|
|
38
|
+
// Builds an RFC 5322 message: multipart/mixed wrapping a multipart/alternative body plus one
|
|
39
|
+
// part per attachment. Bcc recipients are deliberately never written into the headers; they
|
|
40
|
+
// travel only in the SES Destination so other recipients can't see them. boundarySeed exists
|
|
41
|
+
// so tests get deterministic output.
|
|
42
|
+
export const buildRawMimeMessage = (payload, boundarySeed = 'qpq') => {
|
|
43
|
+
const mixedBoundary = `----=_${boundarySeed}_mixed`;
|
|
44
|
+
const alternativeBoundary = `----=_${boundarySeed}_alternative`;
|
|
45
|
+
const headers = [
|
|
46
|
+
`From: ${encodeHeaderText(payload.from)}`,
|
|
47
|
+
`To: ${payload.to.map(encodeHeaderText).join(', ')}`,
|
|
48
|
+
...(payload.cc?.length ? [`Cc: ${payload.cc.map(encodeHeaderText).join(', ')}`] : []),
|
|
49
|
+
...(payload.replyTo?.length ? [`Reply-To: ${payload.replyTo.map(encodeHeaderText).join(', ')}`] : []),
|
|
50
|
+
`Subject: ${encodeHeaderText(payload.subject)}`,
|
|
51
|
+
'MIME-Version: 1.0',
|
|
52
|
+
`Content-Type: multipart/mixed; boundary="${mixedBoundary}"`,
|
|
53
|
+
];
|
|
54
|
+
const bodyParts = [
|
|
55
|
+
...(payload.bodyText ? buildBodyPart('text/plain', payload.bodyText, alternativeBoundary) : []),
|
|
56
|
+
...(payload.bodyHtml ? buildBodyPart('text/html', payload.bodyHtml, alternativeBoundary) : []),
|
|
57
|
+
];
|
|
58
|
+
const attachmentParts = (payload.attachments || []).flatMap((attachment) => buildAttachmentPart(attachment, mixedBoundary));
|
|
59
|
+
const lines = [
|
|
60
|
+
...headers,
|
|
61
|
+
'',
|
|
62
|
+
`--${mixedBoundary}`,
|
|
63
|
+
`Content-Type: multipart/alternative; boundary="${alternativeBoundary}"`,
|
|
64
|
+
'',
|
|
65
|
+
...bodyParts,
|
|
66
|
+
`--${alternativeBoundary}--`,
|
|
67
|
+
'',
|
|
68
|
+
...attachmentParts,
|
|
69
|
+
`--${mixedBoundary}--`,
|
|
70
|
+
'',
|
|
71
|
+
];
|
|
72
|
+
return lines.join(CRLF);
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=buildRawMimeMessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildRawMimeMessage.js","sourceRoot":"","sources":["../../../../src/logic/sesV2/buildRawMimeMessage.ts"],"names":[],"mappings":"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","sourcesContent":["import { QPQBinaryData } from 'quidproquo-core';\nimport { EmailSendEmailActionPayload } from 'quidproquo-webserver';\n\nconst CRLF = '\\r\\n';\n\n// Header values must never contain CR/LF, or a caller-supplied string could inject extra headers\nconst sanitizeHeaderValue = (value: string): string => value.replace(/[\\r\\n]+/g, ' ').trim();\n\n// RFC 2047 encoded-word for header text that isn't printable ascii (e.g. subjects with emoji)\nconst encodeHeaderText = (value: string): string => {\n const sanitized = sanitizeHeaderValue(value);\n\n if (/^[\\x20-\\x7e]*$/.test(sanitized)) {\n return sanitized;\n }\n\n return `=?UTF-8?B?${Buffer.from(sanitized, 'utf8').toString('base64')}?=`;\n};\n\n// RFC 2045 requires base64 body lines of at most 76 characters\nconst wrapBase64 = (base64Data: string): string => {\n const lines = base64Data.match(/.{1,76}/g) || [];\n\n return lines.join(CRLF);\n};\n\nconst buildBodyPart = (contentType: string, content: string, boundary: string): string[] => [\n `--${boundary}`,\n `Content-Type: ${contentType}; charset=UTF-8`,\n 'Content-Transfer-Encoding: base64',\n '',\n wrapBase64(Buffer.from(content, 'utf8').toString('base64')),\n];\n\nconst buildAttachmentPart = (attachment: QPQBinaryData, boundary: string): string[] => {\n const filename = encodeHeaderText(attachment.filename);\n const contentDisposition = attachment.contentDisposition\n ? sanitizeHeaderValue(attachment.contentDisposition)\n : `attachment; filename=\"${filename}\"`;\n\n return [\n `--${boundary}`,\n `Content-Type: ${sanitizeHeaderValue(attachment.mimetype || 'application/octet-stream')}; name=\"${filename}\"`,\n 'Content-Transfer-Encoding: base64',\n `Content-Disposition: ${contentDisposition}`,\n '',\n wrapBase64(attachment.base64Data),\n ];\n};\n\n// Builds an RFC 5322 message: multipart/mixed wrapping a multipart/alternative body plus one\n// part per attachment. Bcc recipients are deliberately never written into the headers; they\n// travel only in the SES Destination so other recipients can't see them. boundarySeed exists\n// so tests get deterministic output.\nexport const buildRawMimeMessage = (payload: EmailSendEmailActionPayload, boundarySeed: string = 'qpq'): string => {\n const mixedBoundary = `----=_${boundarySeed}_mixed`;\n const alternativeBoundary = `----=_${boundarySeed}_alternative`;\n\n const headers = [\n `From: ${encodeHeaderText(payload.from)}`,\n `To: ${payload.to.map(encodeHeaderText).join(', ')}`,\n ...(payload.cc?.length ? [`Cc: ${payload.cc.map(encodeHeaderText).join(', ')}`] : []),\n ...(payload.replyTo?.length ? [`Reply-To: ${payload.replyTo.map(encodeHeaderText).join(', ')}`] : []),\n `Subject: ${encodeHeaderText(payload.subject)}`,\n 'MIME-Version: 1.0',\n `Content-Type: multipart/mixed; boundary=\"${mixedBoundary}\"`,\n ];\n\n const bodyParts = [\n ...(payload.bodyText ? buildBodyPart('text/plain', payload.bodyText, alternativeBoundary) : []),\n ...(payload.bodyHtml ? buildBodyPart('text/html', payload.bodyHtml, alternativeBoundary) : []),\n ];\n\n const attachmentParts = (payload.attachments || []).flatMap((attachment) => buildAttachmentPart(attachment, mixedBoundary));\n\n const lines = [\n ...headers,\n '',\n `--${mixedBoundary}`,\n `Content-Type: multipart/alternative; boundary=\"${alternativeBoundary}\"`,\n '',\n ...bodyParts,\n `--${alternativeBoundary}--`,\n '',\n ...attachmentParts,\n `--${mixedBoundary}--`,\n '',\n ];\n\n return lines.join(CRLF);\n};\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';
|
|
2
|
+
import { createAwsClient } from '../createAwsClient';
|
|
3
|
+
import { buildRawMimeMessage } from './buildRawMimeMessage';
|
|
4
|
+
// SES Simple content can't carry attachments, so those emails go through the raw MIME path.
|
|
5
|
+
// Either way the recipients come from Destination below, which is what keeps bcc addresses
|
|
6
|
+
// out of the message the recipients see.
|
|
7
|
+
const getEmailContent = (payload) => {
|
|
8
|
+
if (payload.attachments?.length) {
|
|
9
|
+
return {
|
|
10
|
+
Raw: { Data: new TextEncoder().encode(buildRawMimeMessage(payload)) },
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
Simple: {
|
|
15
|
+
Subject: { Data: payload.subject, Charset: 'UTF-8' },
|
|
16
|
+
Body: {
|
|
17
|
+
...(payload.bodyText ? { Text: { Data: payload.bodyText, Charset: 'UTF-8' } } : {}),
|
|
18
|
+
...(payload.bodyHtml ? { Html: { Data: payload.bodyHtml, Charset: 'UTF-8' } } : {}),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export const sendEmail = async (payload, region) => {
|
|
24
|
+
const sesClient = createAwsClient(SESv2Client, { region });
|
|
25
|
+
const response = await sesClient.send(new SendEmailCommand({
|
|
26
|
+
FromEmailAddress: payload.from,
|
|
27
|
+
Destination: {
|
|
28
|
+
ToAddresses: payload.to,
|
|
29
|
+
CcAddresses: payload.cc,
|
|
30
|
+
BccAddresses: payload.bcc,
|
|
31
|
+
},
|
|
32
|
+
ReplyToAddresses: payload.replyTo,
|
|
33
|
+
Content: getEmailContent(payload),
|
|
34
|
+
}));
|
|
35
|
+
return response.MessageId || '';
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=sendEmail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sendEmail.js","sourceRoot":"","sources":["../../../../src/logic/sesV2/sendEmail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,gBAAgB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpF,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,4FAA4F;AAC5F,2FAA2F;AAC3F,yCAAyC;AACzC,MAAM,eAAe,GAAG,CAAC,OAAoC,EAAgB,EAAE;IAC7E,IAAI,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;QAChC,OAAO;YACL,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,EAAE;SACtE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE;YACN,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;YACpD,IAAI,EAAE;gBACJ,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnF,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,OAAoC,EAAE,MAAc,EAAmB,EAAE;IACvG,MAAM,SAAS,GAAG,eAAe,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAE3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,IAAI,CACnC,IAAI,gBAAgB,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC,IAAI;QAC9B,WAAW,EAAE;YACX,WAAW,EAAE,OAAO,CAAC,EAAE;YACvB,WAAW,EAAE,OAAO,CAAC,EAAE;YACvB,YAAY,EAAE,OAAO,CAAC,GAAG;SAC1B;QACD,gBAAgB,EAAE,OAAO,CAAC,OAAO;QACjC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC;KAClC,CAAC,CACH,CAAC;IAEF,OAAO,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC;AAClC,CAAC,CAAC","sourcesContent":["import { EmailSendEmailActionPayload } from 'quidproquo-webserver';\n\nimport { EmailContent, SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';\n\nimport { createAwsClient } from '../createAwsClient';\nimport { buildRawMimeMessage } from './buildRawMimeMessage';\n\n// SES Simple content can't carry attachments, so those emails go through the raw MIME path.\n// Either way the recipients come from Destination below, which is what keeps bcc addresses\n// out of the message the recipients see.\nconst getEmailContent = (payload: EmailSendEmailActionPayload): EmailContent => {\n if (payload.attachments?.length) {\n return {\n Raw: { Data: new TextEncoder().encode(buildRawMimeMessage(payload)) },\n };\n }\n\n return {\n Simple: {\n Subject: { Data: payload.subject, Charset: 'UTF-8' },\n Body: {\n ...(payload.bodyText ? { Text: { Data: payload.bodyText, Charset: 'UTF-8' } } : {}),\n ...(payload.bodyHtml ? { Html: { Data: payload.bodyHtml, Charset: 'UTF-8' } } : {}),\n },\n },\n };\n};\n\nexport const sendEmail = async (payload: EmailSendEmailActionPayload, region: string): Promise<string> => {\n const sesClient = createAwsClient(SESv2Client, { region });\n\n const response = await sesClient.send(\n new SendEmailCommand({\n FromEmailAddress: payload.from,\n Destination: {\n ToAddresses: payload.to,\n CcAddresses: payload.cc,\n BccAddresses: payload.bcc,\n },\n ReplyToAddresses: payload.replyTo,\n Content: getEmailContent(payload),\n }),\n );\n\n return response.MessageId || '';\n};\n"]}
|