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.
Files changed (38) hide show
  1. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.d.ts +2 -0
  2. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js +43 -0
  3. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js.map +1 -0
  4. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.d.ts +2 -0
  5. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js +26 -0
  6. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js.map +1 -0
  7. package/lib/commonjs/getActionProcessor/webserver/email/index.d.ts +2 -0
  8. package/lib/commonjs/getActionProcessor/webserver/email/index.js +19 -0
  9. package/lib/commonjs/getActionProcessor/webserver/email/index.js.map +1 -0
  10. package/lib/commonjs/getActionProcessor/webserver/index.d.ts +1 -0
  11. package/lib/commonjs/getActionProcessor/webserver/index.js +3 -1
  12. package/lib/commonjs/getActionProcessor/webserver/index.js.map +1 -1
  13. package/lib/commonjs/logic/sesV2/buildRawMimeMessage.d.ts +2 -0
  14. package/lib/commonjs/logic/sesV2/buildRawMimeMessage.js +79 -0
  15. package/lib/commonjs/logic/sesV2/buildRawMimeMessage.js.map +1 -0
  16. package/lib/commonjs/logic/sesV2/sendEmail.d.ts +2 -0
  17. package/lib/commonjs/logic/sesV2/sendEmail.js +48 -0
  18. package/lib/commonjs/logic/sesV2/sendEmail.js.map +1 -0
  19. package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.d.ts +2 -0
  20. package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js +28 -0
  21. package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js.map +1 -0
  22. package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.d.ts +2 -0
  23. package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js +11 -0
  24. package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js.map +1 -0
  25. package/lib/esm/getActionProcessor/webserver/email/index.d.ts +2 -0
  26. package/lib/esm/getActionProcessor/webserver/email/index.js +7 -0
  27. package/lib/esm/getActionProcessor/webserver/email/index.js.map +1 -0
  28. package/lib/esm/getActionProcessor/webserver/index.d.ts +1 -0
  29. package/lib/esm/getActionProcessor/webserver/index.js +3 -0
  30. package/lib/esm/getActionProcessor/webserver/index.js.map +1 -1
  31. package/lib/esm/logic/sesV2/buildRawMimeMessage.d.ts +2 -0
  32. package/lib/esm/logic/sesV2/buildRawMimeMessage.js +74 -0
  33. package/lib/esm/logic/sesV2/buildRawMimeMessage.js.map +1 -0
  34. package/lib/esm/logic/sesV2/sendEmail.d.ts +2 -0
  35. package/lib/esm/logic/sesV2/sendEmail.js +37 -0
  36. package/lib/esm/logic/sesV2/sendEmail.js.map +1 -0
  37. package/lib/extension-layer/qpq-log-extension/index.js +22 -22
  38. package/package.json +25 -24
@@ -0,0 +1,2 @@
1
+ import { ActionProcessorListResolver } from 'quidproquo-core';
2
+ export declare const getEmailSendEmailActionProcessor: ActionProcessorListResolver;
@@ -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
@@ -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"]}
@@ -0,0 +1,2 @@
1
+ import { ActionProcessorListResolver } from 'quidproquo-core';
2
+ export declare const getEmailSetDeliveryStatusActionProcessor: ActionProcessorListResolver;
@@ -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,2 @@
1
+ import { ActionProcessorListResolver } from 'quidproquo-core';
2
+ export declare const getEmailActionProcessor: ActionProcessorListResolver;
@@ -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"]}
@@ -1,3 +1,4 @@
1
+ export * from './email';
1
2
  export * from './serviceFunction';
2
3
  export * from './webEntry';
3
4
  export * from './websocket';
@@ -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,+CAC9B,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;AAPU,QAAA,2BAA2B,+BAOrC","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,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,2 @@
1
+ import { EmailSendEmailActionPayload } from 'quidproquo-webserver';
2
+ export declare const buildRawMimeMessage: (payload: EmailSendEmailActionPayload, boundarySeed?: string) => string;
@@ -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,2 @@
1
+ import { EmailSendEmailActionPayload } from 'quidproquo-webserver';
2
+ export declare const sendEmail: (payload: EmailSendEmailActionPayload, region: string) => Promise<string>;
@@ -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,2 @@
1
+ import { ActionProcessorListResolver } from 'quidproquo-core';
2
+ export declare const getEmailSendEmailActionProcessor: ActionProcessorListResolver;
@@ -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"]}
@@ -0,0 +1,2 @@
1
+ import { ActionProcessorListResolver } from 'quidproquo-core';
2
+ export declare const getEmailSetDeliveryStatusActionProcessor: ActionProcessorListResolver;
@@ -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
@@ -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,2 @@
1
+ import { ActionProcessorListResolver } from 'quidproquo-core';
2
+ export declare const getEmailActionProcessor: ActionProcessorListResolver;
@@ -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,3 +1,4 @@
1
+ export * from './email';
1
2
  export * from './serviceFunction';
2
3
  export * from './webEntry';
3
4
  export * from './websocket';
@@ -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,2 @@
1
+ import { EmailSendEmailActionPayload } from 'quidproquo-webserver';
2
+ export declare const buildRawMimeMessage: (payload: EmailSendEmailActionPayload, boundarySeed?: string) => string;
@@ -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,2 @@
1
+ import { EmailSendEmailActionPayload } from 'quidproquo-webserver';
2
+ export declare const sendEmail: (payload: EmailSendEmailActionPayload, region: string) => Promise<string>;
@@ -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"]}