@headless-lms/adapter-email-ses 0.1.0 → 0.1.1

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/dist/index.js CHANGED
@@ -1,5 +1,17 @@
1
1
  import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
2
+ import { EmailRejectedError } from "@headless-lms/core/shared/mailer";
2
3
  //#region src/index.ts
4
+ const THROTTLED = /* @__PURE__ */ new Set([
5
+ "TooManyRequestsException",
6
+ "LimitExceededException",
7
+ "ThrottlingException"
8
+ ]);
9
+ function rejected(err) {
10
+ const failure = err ?? {};
11
+ const status = failure.$metadata?.httpStatusCode;
12
+ if (failure.$fault !== "client" || failure.$retryable || status === 429 || THROTTLED.has(failure.name ?? "")) return null;
13
+ return new EmailRejectedError(`ses ${failure.name ?? "error"}${status ? ` ${status}` : ""}: ${failure.message ?? ""}`, status);
14
+ }
3
15
  var SesEmailAdapter = class {
4
16
  config;
5
17
  logger;
@@ -40,13 +52,8 @@ var SesEmailAdapter = class {
40
52
  try {
41
53
  await this.client.send(command);
42
54
  } catch (err) {
43
- const e = err;
44
- this.logger?.error("email send failed", {
45
- to: message.to,
46
- name: e?.name ?? "",
47
- detail: e?.message ?? ""
48
- });
49
- throw err;
55
+ this.logger?.error("email send failed", { err });
56
+ throw rejected(err) ?? err;
50
57
  }
51
58
  }
52
59
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// AWS SES implementation of the EmailSender port.\nimport { SendEmailCommand, SESv2Client } from \"@aws-sdk/client-sesv2\";\nimport type { EmailSender, EmailMessage, Logger } from \"@headless-lms/core/types\";\n\nexport interface SesEmailConfig {\n region: string;\n /** Sender, e.g. \"Acme LMS <noreply@acme.com>\". Must be a verified SES identity. */\n from: string;\n /** Omit both keys to use the default AWS credential chain (IAM role, env, profile). */\n accessKeyId?: string;\n secretAccessKey?: string;\n /** SES configuration set to send through. */\n configurationSetName?: string;\n}\n\ninterface SesClient {\n send(command: SendEmailCommand): Promise<unknown>;\n}\n\nexport class SesEmailAdapter implements EmailSender {\n private readonly client: SesClient;\n\n constructor(\n private readonly config: SesEmailConfig,\n private readonly logger?: Logger,\n client?: SesClient,\n ) {\n this.client =\n client ??\n new SESv2Client({\n region: config.region,\n ...(config.accessKeyId && config.secretAccessKey\n ? {\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n }\n : {}),\n });\n }\n\n async send(message: EmailMessage): Promise<void> {\n const command = new SendEmailCommand({\n FromEmailAddress: this.config.from,\n Destination: { ToAddresses: [message.to] },\n ...(this.config.configurationSetName\n ? { ConfigurationSetName: this.config.configurationSetName }\n : {}),\n Content: {\n Simple: {\n Subject: { Data: message.subject, Charset: \"UTF-8\" },\n Body: {\n Text: { Data: message.text, Charset: \"UTF-8\" },\n ...(message.html !== undefined\n ? { Html: { Data: message.html, Charset: \"UTF-8\" } }\n : {}),\n },\n },\n },\n });\n try {\n await this.client.send(command);\n } catch (err) {\n const e = err as { name?: string; message?: string };\n this.logger?.error(\"email send failed\", {\n to: message.to,\n name: e?.name ?? \"\",\n detail: e?.message ?? \"\",\n });\n throw err;\n }\n }\n}\n"],"mappings":";;AAmBA,IAAa,kBAAb,MAAoD;CAI/B;CACA;CAJnB;CAEA,YACE,QACA,QACA,QACA;EAHiB,KAAA,SAAA;EACA,KAAA,SAAA;EAGjB,KAAK,SACH,UACA,IAAI,YAAY;GACd,QAAQ,OAAO;GACf,GAAI,OAAO,eAAe,OAAO,kBAC7B,EACE,aAAa;IACX,aAAa,OAAO;IACpB,iBAAiB,OAAO;GAC1B,EACF,IACA,CAAC;EACP,CAAC;CACL;CAEA,MAAM,KAAK,SAAsC;EAC/C,MAAM,UAAU,IAAI,iBAAiB;GACnC,kBAAkB,KAAK,OAAO;GAC9B,aAAa,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAE;GACzC,GAAI,KAAK,OAAO,uBACZ,EAAE,sBAAsB,KAAK,OAAO,qBAAqB,IACzD,CAAC;GACL,SAAS,EACP,QAAQ;IACN,SAAS;KAAE,MAAM,QAAQ;KAAS,SAAS;IAAQ;IACnD,MAAM;KACJ,MAAM;MAAE,MAAM,QAAQ;MAAM,SAAS;KAAQ;KAC7C,GAAI,QAAQ,SAAS,KAAA,IACjB,EAAE,MAAM;MAAE,MAAM,QAAQ;MAAM,SAAS;KAAQ,EAAE,IACjD,CAAC;IACP;GACF,EACF;EACF,CAAC;EACD,IAAI;GACF,MAAM,KAAK,OAAO,KAAK,OAAO;EAChC,SAAS,KAAK;GACZ,MAAM,IAAI;GACV,KAAK,QAAQ,MAAM,qBAAqB;IACtC,IAAI,QAAQ;IACZ,MAAM,GAAG,QAAQ;IACjB,QAAQ,GAAG,WAAW;GACxB,CAAC;GACD,MAAM;EACR;CACF;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// AWS SES implementation of the EmailSender port.\nimport { SendEmailCommand, SESv2Client } from \"@aws-sdk/client-sesv2\";\nimport type { EmailSender, EmailMessage, Logger } from \"@headless-lms/core/types\";\nimport { EmailRejectedError } from \"@headless-lms/core/shared/mailer\";\n\nexport interface SesEmailConfig {\n region: string;\n /** Sender, e.g. \"Acme LMS <noreply@acme.com>\". Must be a verified SES identity. */\n from: string;\n /** Omit both keys to use the default AWS credential chain (IAM role, env, profile). */\n accessKeyId?: string;\n secretAccessKey?: string;\n /** SES configuration set to send through. */\n configurationSetName?: string;\n}\n\nconst THROTTLED = new Set([\n \"TooManyRequestsException\",\n \"LimitExceededException\",\n \"ThrottlingException\",\n]);\n\ninterface SesFailure {\n name?: string;\n message?: string;\n $fault?: \"client\" | \"server\";\n $retryable?: unknown;\n $metadata?: { httpStatusCode?: number };\n}\n\nfunction rejected(err: unknown): EmailRejectedError | null {\n const failure = (err ?? {}) as SesFailure;\n const status = failure.$metadata?.httpStatusCode;\n if (\n failure.$fault !== \"client\" ||\n failure.$retryable ||\n status === 429 ||\n THROTTLED.has(failure.name ?? \"\")\n ) {\n return null;\n }\n return new EmailRejectedError(\n `ses ${failure.name ?? \"error\"}${status ? ` ${status}` : \"\"}: ${failure.message ?? \"\"}`,\n status,\n );\n}\n\ninterface SesClient {\n send(command: SendEmailCommand): Promise<unknown>;\n}\n\nexport class SesEmailAdapter implements EmailSender {\n private readonly client: SesClient;\n\n constructor(\n private readonly config: SesEmailConfig,\n private readonly logger?: Logger,\n client?: SesClient,\n ) {\n this.client =\n client ??\n new SESv2Client({\n region: config.region,\n ...(config.accessKeyId && config.secretAccessKey\n ? {\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n }\n : {}),\n });\n }\n\n async send(message: EmailMessage): Promise<void> {\n const command = new SendEmailCommand({\n FromEmailAddress: this.config.from,\n Destination: { ToAddresses: [message.to] },\n ...(this.config.configurationSetName\n ? { ConfigurationSetName: this.config.configurationSetName }\n : {}),\n Content: {\n Simple: {\n Subject: { Data: message.subject, Charset: \"UTF-8\" },\n Body: {\n Text: { Data: message.text, Charset: \"UTF-8\" },\n ...(message.html !== undefined\n ? { Html: { Data: message.html, Charset: \"UTF-8\" } }\n : {}),\n },\n },\n },\n });\n try {\n await this.client.send(command);\n } catch (err) {\n this.logger?.error(\"email send failed\", { err });\n throw rejected(err) ?? err;\n }\n }\n}\n"],"mappings":";;;AAgBA,MAAM,4BAAY,IAAI,IAAI;CACxB;CACA;CACA;AACF,CAAC;AAUD,SAAS,SAAS,KAAyC;CACzD,MAAM,UAAW,OAAO,CAAC;CACzB,MAAM,SAAS,QAAQ,WAAW;CAClC,IACE,QAAQ,WAAW,YACnB,QAAQ,cACR,WAAW,OACX,UAAU,IAAI,QAAQ,QAAQ,EAAE,GAEhC,OAAO;CAET,OAAO,IAAI,mBACT,OAAO,QAAQ,QAAQ,UAAU,SAAS,IAAI,WAAW,GAAG,IAAI,QAAQ,WAAW,MACnF,MACF;AACF;AAMA,IAAa,kBAAb,MAAoD;CAI/B;CACA;CAJnB;CAEA,YACE,QACA,QACA,QACA;EAHiB,KAAA,SAAA;EACA,KAAA,SAAA;EAGjB,KAAK,SACH,UACA,IAAI,YAAY;GACd,QAAQ,OAAO;GACf,GAAI,OAAO,eAAe,OAAO,kBAC7B,EACE,aAAa;IACX,aAAa,OAAO;IACpB,iBAAiB,OAAO;GAC1B,EACF,IACA,CAAC;EACP,CAAC;CACL;CAEA,MAAM,KAAK,SAAsC;EAC/C,MAAM,UAAU,IAAI,iBAAiB;GACnC,kBAAkB,KAAK,OAAO;GAC9B,aAAa,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAE;GACzC,GAAI,KAAK,OAAO,uBACZ,EAAE,sBAAsB,KAAK,OAAO,qBAAqB,IACzD,CAAC;GACL,SAAS,EACP,QAAQ;IACN,SAAS;KAAE,MAAM,QAAQ;KAAS,SAAS;IAAQ;IACnD,MAAM;KACJ,MAAM;MAAE,MAAM,QAAQ;MAAM,SAAS;KAAQ;KAC7C,GAAI,QAAQ,SAAS,KAAA,IACjB,EAAE,MAAM;MAAE,MAAM,QAAQ;MAAM,SAAS;KAAQ,EAAE,IACjD,CAAC;IACP;GACF,EACF;EACF,CAAC;EACD,IAAI;GACF,MAAM,KAAK,OAAO,KAAK,OAAO;EAChC,SAAS,KAAK;GACZ,KAAK,QAAQ,MAAM,qBAAqB,EAAE,IAAI,CAAC;GAC/C,MAAM,SAAS,GAAG,KAAK;EACzB;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-lms/adapter-email-ses",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@aws-sdk/client-sesv2": "^3.712.0",
15
- "@headless-lms/core": "0.1.0"
15
+ "@headless-lms/core": "0.5.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^22.10.2",