@camstack/addon-notifiers 1.2.130 → 1.2.131

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 (3) hide show
  1. package/dist/addon.js +178 -12
  2. package/dist/addon.mjs +178 -12
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13714,7 +13714,14 @@ var BrokerProviderInfoSchema = object({
13714
13714
  /** Broker kinds this provider can create, with a display label. */
13715
13715
  kinds: array(object({
13716
13716
  kind: string(),
13717
- label: string()
13717
+ label: string(),
13718
+ /**
13719
+ * Relative hub path to start an interactive account link (OAuth popup).
13720
+ * May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
13721
+ */
13722
+ interactiveConnectPath: string().min(1).optional(),
13723
+ /** Relative hub path to disconnect the linked account (same popup pattern). */
13724
+ interactiveDisconnectPath: string().min(1).optional()
13718
13725
  }))
13719
13726
  });
13720
13727
  var ListInputSchema = object({
@@ -51976,18 +51983,18 @@ var HTML_ESCAPES = [
51976
51983
  [/"/g, """],
51977
51984
  [/'/g, "'"]
51978
51985
  ];
51979
- function escapeHtml$1(text) {
51986
+ function escapeHtml$2(text) {
51980
51987
  let out = text;
51981
51988
  for (const [re, rep] of HTML_ESCAPES) out = out.replace(re, rep);
51982
51989
  return out;
51983
51990
  }
51984
51991
  /** Plain text → HTML-lite (escape + newlines to <br>). */
51985
51992
  function textToHtml(text) {
51986
- return escapeHtml$1(text).replace(/\n/g, "<br>");
51993
+ return escapeHtml$2(text).replace(/\n/g, "<br>");
51987
51994
  }
51988
51995
  /** Markdown → HTML-lite (bold/italic/code/links; used e.g. for Pushover). */
51989
51996
  function markdownToHtmlLite(md) {
51990
- return escapeHtml$1(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51997
+ return escapeHtml$2(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51991
51998
  }
51992
51999
  /**
51993
52000
  * Preference order when the source format is NOT supported — first supported
@@ -53345,6 +53352,116 @@ var discordAdapter = {
53345
53352
  };
53346
53353
  }
53347
53354
  };
53355
+ var descriptor$2 = {
53356
+ kind: "email",
53357
+ label: "Email",
53358
+ icon: "mail",
53359
+ addonId: "notifiers",
53360
+ supportsDiscovery: false,
53361
+ configSchema: { sections: [{
53362
+ id: "email",
53363
+ title: "Email",
53364
+ description: "Send via a Connectivity → Email provider (Google Services or SMTP). Install and Verify a provider first.",
53365
+ fields: [
53366
+ {
53367
+ type: "text",
53368
+ key: "to",
53369
+ label: "To",
53370
+ description: "Recipient address, or comma-separated list.",
53371
+ required: true
53372
+ },
53373
+ {
53374
+ type: "text",
53375
+ key: "smtpAddonId",
53376
+ label: "Email provider addon id (optional)",
53377
+ description: "Leave blank to use the first ready smtp-provider. Examples: google-services, smtp-nodemailer."
53378
+ },
53379
+ {
53380
+ type: "text",
53381
+ key: "from",
53382
+ label: "From (optional)",
53383
+ description: "Override From when the relay allows it."
53384
+ }
53385
+ ]
53386
+ }] },
53387
+ caps: {
53388
+ attachments: {
53389
+ mediaTypes: ["image"],
53390
+ mode: "url",
53391
+ max: 0
53392
+ },
53393
+ actions: 0,
53394
+ levels: [{
53395
+ id: "normal",
53396
+ label: "Normal",
53397
+ ordinal: 3
53398
+ }, {
53399
+ id: "high",
53400
+ label: "High",
53401
+ ordinal: 4
53402
+ }],
53403
+ format: [
53404
+ "text",
53405
+ "html",
53406
+ "markdown"
53407
+ ],
53408
+ clickUrl: true,
53409
+ sound: false,
53410
+ ttl: false,
53411
+ bodyMaxLen: 1e5
53412
+ }
53413
+ };
53414
+ function requireTo(config) {
53415
+ const raw = config["to"];
53416
+ if (typeof raw !== "string" || raw.trim() === "") throw new Error("email: missing required config 'to'");
53417
+ const list = raw.split(/[,;\s]+/).map((s) => s.trim()).filter((s) => s.length > 0);
53418
+ if (list.length === 0) throw new Error("email: missing required config 'to'");
53419
+ return list;
53420
+ }
53421
+ function renderBodies(prepared) {
53422
+ const body = prepared.bodyParts[0] ?? prepared.body;
53423
+ const title = prepared.title?.trim() ?? "";
53424
+ const click = prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `\n\n${prepared.clickUrl}` : "";
53425
+ const text = title !== "" ? `${title}\n\n${body}${click}` : `${body}${click}`;
53426
+ if (prepared.format === "html") return {
53427
+ text,
53428
+ html: `${title !== "" ? `<h2>${escapeHtml$1(title)}</h2>` : ""}<div>${body}</div>${prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `<p><a href="${escapeHtml$1(prepared.clickUrl)}">${escapeHtml$1(prepared.clickUrl)}</a></p>` : ""}`
53429
+ };
53430
+ return { text };
53431
+ }
53432
+ function escapeHtml$1(value) {
53433
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
53434
+ }
53435
+ /** Build the email adapter bound to a live smtp-provider client. */
53436
+ function createEmailAdapter(smtp) {
53437
+ return {
53438
+ descriptor: descriptor$2,
53439
+ destinationKeys: ["to"],
53440
+ async sendDirect(config, prepared) {
53441
+ try {
53442
+ const to = requireTo(config);
53443
+ const bodies = renderBodies(prepared);
53444
+ const addonId = (typeof config["smtpAddonId"] === "string" && config["smtpAddonId"].trim() !== "" ? config["smtpAddonId"].trim() : void 0) ?? await smtp.resolveDefaultAddonId?.();
53445
+ const subject = prepared.title?.trim() && prepared.title.trim().length > 0 ? prepared.title.trim() : "CamStack notification";
53446
+ const from = typeof config["from"] === "string" && config["from"].trim() !== "" ? config["from"].trim() : void 0;
53447
+ await smtp.sendEmail({
53448
+ to,
53449
+ subject,
53450
+ text: bodies.text,
53451
+ ...bodies.html !== void 0 ? { html: bodies.html } : {},
53452
+ ...addonId !== void 0 ? { addonId } : {},
53453
+ ...from !== void 0 ? { from } : {}
53454
+ });
53455
+ return { success: true };
53456
+ } catch (err) {
53457
+ return {
53458
+ success: false,
53459
+ error: err instanceof Error ? err.message : String(err)
53460
+ };
53461
+ }
53462
+ }
53463
+ };
53464
+ }
53348
53465
  //#endregion
53349
53466
  //#region src/http.ts
53350
53467
  /** Default per-request timeout for notification dispatch. */
@@ -55280,7 +55397,8 @@ function zentikConfigSchema() {
55280
55397
  }
55281
55398
  //#endregion
55282
55399
  //#region src/adapters/registry.ts
55283
- var NOTIFIER_ADAPTERS = [
55400
+ /** HTTP adapters that need no runtime deps. */
55401
+ var HTTP_NOTIFIER_ADAPTERS = [
55284
55402
  ntfyAdapter,
55285
55403
  pushoverAdapter,
55286
55404
  gotifyAdapter,
@@ -55432,6 +55550,17 @@ var NOTIFIER_ADAPTERS = [
55432
55550
  },
55433
55551
  whatsappAdapter
55434
55552
  ];
55553
+ /**
55554
+ * Full catalog. When `smtp` is omitted the Email kind is still listed but
55555
+ * `sendDirect` fails until the addon wires a client (tests use this).
55556
+ */
55557
+ function buildNotifierAdapters(smtp) {
55558
+ const emailSmtp = smtp ?? { async sendEmail() {
55559
+ throw new Error("email: no smtp-provider client — install Google Services or SMTP Email under Connectivity → Email and Verify it");
55560
+ } };
55561
+ return [...HTTP_NOTIFIER_ADAPTERS, createEmailAdapter(emailSmtp)];
55562
+ }
55563
+ buildNotifierAdapters();
55435
55564
  var NOTIFIER_ICONS = {
55436
55565
  telegram: {
55437
55566
  contentType: "image/svg+xml",
@@ -56273,6 +56402,32 @@ function createNotificationOutputProvider(deps) {
56273
56402
  ...prepared,
56274
56403
  attachments
56275
56404
  };
56405
+ if (adapter.sendDirect) try {
56406
+ const result = await adapter.sendDirect(target.config, readyPrepared);
56407
+ return result.error === void 0 ? {
56408
+ ...result,
56409
+ renderedAs: prepared.renderedAs
56410
+ } : {
56411
+ ...result,
56412
+ error: stripUrls(result.error),
56413
+ renderedAs: prepared.renderedAs
56414
+ };
56415
+ } catch (err) {
56416
+ logger?.warn("sendDirect failed", { meta: {
56417
+ kind: target.kind,
56418
+ error: errMsg(err)
56419
+ } });
56420
+ return {
56421
+ success: false,
56422
+ error: `sendDirect failed: ${errMsg(err)}`,
56423
+ renderedAs: prepared.renderedAs
56424
+ };
56425
+ }
56426
+ if (!adapter.buildRequest) return {
56427
+ success: false,
56428
+ error: `No adapter send path for kind '${target.kind}'`,
56429
+ renderedAs: prepared.renderedAs
56430
+ };
56276
56431
  let plan;
56277
56432
  try {
56278
56433
  plan = adapter.buildRequest(target.config, readyPrepared);
@@ -56640,11 +56795,12 @@ var TargetStore = class {
56640
56795
  * Notifiers addon — a system `notification-output` collection provider.
56641
56796
  *
56642
56797
  * It hosts per-kind `NotifierAdapter`s (ntfy, pushover, gotify, telegram,
56643
- * discord, webhook, zentik, whatsapp) behind one uniform cap surface. Targets
56644
- * persist via the `settings-store` cap; the canonical notification is degraded
56645
- * per each target's declared capabilities by the pure `prepareNotification`
56646
- * engine in `@camstack/types`. The catalog is `NOTIFIER_ADAPTERS`
56647
- * (`adapters/registry.ts`).
56798
+ * discord, webhook, zentik, whatsapp, email) behind one uniform cap surface.
56799
+ * Targets persist via the `settings-store` cap; the canonical notification is
56800
+ * degraded per each target's declared capabilities by the pure
56801
+ * `prepareNotification` engine in `@camstack/types`. The catalog is
56802
+ * `buildNotifierAdapters` (`adapters/registry.ts`); email uses `sendDirect` →
56803
+ * `smtp-provider`.
56648
56804
  */
56649
56805
  var NotifiersAddon = class extends BaseAddon {
56650
56806
  constructor() {
@@ -56656,9 +56812,19 @@ var NotifiersAddon = class extends BaseAddon {
56656
56812
  if (!api) this.ctx.logger.warn("notifiers: no ctx.api — targets persist in-memory only");
56657
56813
  const store = new TargetStore(port);
56658
56814
  await store.init();
56815
+ const adapters = buildNotifierAdapters(api !== void 0 ? { sendEmail: async (input) => {
56816
+ return { messageId: (await api.smtpProvider.sendEmail({
56817
+ to: input.to,
56818
+ subject: input.subject,
56819
+ text: input.text,
56820
+ ...input.html !== void 0 ? { html: input.html } : {},
56821
+ ...input.from !== void 0 ? { from: input.from } : {},
56822
+ ...input.addonId !== void 0 ? { addonId: input.addonId } : {}
56823
+ })).messageId };
56824
+ } } : void 0);
56659
56825
  const provider = createNotificationOutputProvider({
56660
56826
  store,
56661
- adapters: NOTIFIER_ADAPTERS,
56827
+ adapters,
56662
56828
  logger: this.ctx.logger.child("notification-output"),
56663
56829
  iconUrlFor,
56664
56830
  iconMediaTypeFor
@@ -56668,7 +56834,7 @@ var NotifiersAddon = class extends BaseAddon {
56668
56834
  provider,
56669
56835
  logger: this.ctx.logger.child("nc-target-actions")
56670
56836
  });
56671
- this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: NOTIFIER_ADAPTERS.length } });
56837
+ this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: adapters.length } });
56672
56838
  return {
56673
56839
  providers: [{
56674
56840
  capability: notificationOutputCapability,
package/dist/addon.mjs CHANGED
@@ -13687,7 +13687,14 @@ var BrokerProviderInfoSchema = object({
13687
13687
  /** Broker kinds this provider can create, with a display label. */
13688
13688
  kinds: array(object({
13689
13689
  kind: string(),
13690
- label: string()
13690
+ label: string(),
13691
+ /**
13692
+ * Relative hub path to start an interactive account link (OAuth popup).
13693
+ * May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
13694
+ */
13695
+ interactiveConnectPath: string().min(1).optional(),
13696
+ /** Relative hub path to disconnect the linked account (same popup pattern). */
13697
+ interactiveDisconnectPath: string().min(1).optional()
13691
13698
  }))
13692
13699
  });
13693
13700
  var ListInputSchema = object({
@@ -51949,18 +51956,18 @@ var HTML_ESCAPES = [
51949
51956
  [/"/g, "&quot;"],
51950
51957
  [/'/g, "&#39;"]
51951
51958
  ];
51952
- function escapeHtml$1(text) {
51959
+ function escapeHtml$2(text) {
51953
51960
  let out = text;
51954
51961
  for (const [re, rep] of HTML_ESCAPES) out = out.replace(re, rep);
51955
51962
  return out;
51956
51963
  }
51957
51964
  /** Plain text → HTML-lite (escape + newlines to <br>). */
51958
51965
  function textToHtml(text) {
51959
- return escapeHtml$1(text).replace(/\n/g, "<br>");
51966
+ return escapeHtml$2(text).replace(/\n/g, "<br>");
51960
51967
  }
51961
51968
  /** Markdown → HTML-lite (bold/italic/code/links; used e.g. for Pushover). */
51962
51969
  function markdownToHtmlLite(md) {
51963
- return escapeHtml$1(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51970
+ return escapeHtml$2(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51964
51971
  }
51965
51972
  /**
51966
51973
  * Preference order when the source format is NOT supported — first supported
@@ -53318,6 +53325,116 @@ var discordAdapter = {
53318
53325
  };
53319
53326
  }
53320
53327
  };
53328
+ var descriptor$2 = {
53329
+ kind: "email",
53330
+ label: "Email",
53331
+ icon: "mail",
53332
+ addonId: "notifiers",
53333
+ supportsDiscovery: false,
53334
+ configSchema: { sections: [{
53335
+ id: "email",
53336
+ title: "Email",
53337
+ description: "Send via a Connectivity → Email provider (Google Services or SMTP). Install and Verify a provider first.",
53338
+ fields: [
53339
+ {
53340
+ type: "text",
53341
+ key: "to",
53342
+ label: "To",
53343
+ description: "Recipient address, or comma-separated list.",
53344
+ required: true
53345
+ },
53346
+ {
53347
+ type: "text",
53348
+ key: "smtpAddonId",
53349
+ label: "Email provider addon id (optional)",
53350
+ description: "Leave blank to use the first ready smtp-provider. Examples: google-services, smtp-nodemailer."
53351
+ },
53352
+ {
53353
+ type: "text",
53354
+ key: "from",
53355
+ label: "From (optional)",
53356
+ description: "Override From when the relay allows it."
53357
+ }
53358
+ ]
53359
+ }] },
53360
+ caps: {
53361
+ attachments: {
53362
+ mediaTypes: ["image"],
53363
+ mode: "url",
53364
+ max: 0
53365
+ },
53366
+ actions: 0,
53367
+ levels: [{
53368
+ id: "normal",
53369
+ label: "Normal",
53370
+ ordinal: 3
53371
+ }, {
53372
+ id: "high",
53373
+ label: "High",
53374
+ ordinal: 4
53375
+ }],
53376
+ format: [
53377
+ "text",
53378
+ "html",
53379
+ "markdown"
53380
+ ],
53381
+ clickUrl: true,
53382
+ sound: false,
53383
+ ttl: false,
53384
+ bodyMaxLen: 1e5
53385
+ }
53386
+ };
53387
+ function requireTo(config) {
53388
+ const raw = config["to"];
53389
+ if (typeof raw !== "string" || raw.trim() === "") throw new Error("email: missing required config 'to'");
53390
+ const list = raw.split(/[,;\s]+/).map((s) => s.trim()).filter((s) => s.length > 0);
53391
+ if (list.length === 0) throw new Error("email: missing required config 'to'");
53392
+ return list;
53393
+ }
53394
+ function renderBodies(prepared) {
53395
+ const body = prepared.bodyParts[0] ?? prepared.body;
53396
+ const title = prepared.title?.trim() ?? "";
53397
+ const click = prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `\n\n${prepared.clickUrl}` : "";
53398
+ const text = title !== "" ? `${title}\n\n${body}${click}` : `${body}${click}`;
53399
+ if (prepared.format === "html") return {
53400
+ text,
53401
+ html: `${title !== "" ? `<h2>${escapeHtml$1(title)}</h2>` : ""}<div>${body}</div>${prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `<p><a href="${escapeHtml$1(prepared.clickUrl)}">${escapeHtml$1(prepared.clickUrl)}</a></p>` : ""}`
53402
+ };
53403
+ return { text };
53404
+ }
53405
+ function escapeHtml$1(value) {
53406
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
53407
+ }
53408
+ /** Build the email adapter bound to a live smtp-provider client. */
53409
+ function createEmailAdapter(smtp) {
53410
+ return {
53411
+ descriptor: descriptor$2,
53412
+ destinationKeys: ["to"],
53413
+ async sendDirect(config, prepared) {
53414
+ try {
53415
+ const to = requireTo(config);
53416
+ const bodies = renderBodies(prepared);
53417
+ const addonId = (typeof config["smtpAddonId"] === "string" && config["smtpAddonId"].trim() !== "" ? config["smtpAddonId"].trim() : void 0) ?? await smtp.resolveDefaultAddonId?.();
53418
+ const subject = prepared.title?.trim() && prepared.title.trim().length > 0 ? prepared.title.trim() : "CamStack notification";
53419
+ const from = typeof config["from"] === "string" && config["from"].trim() !== "" ? config["from"].trim() : void 0;
53420
+ await smtp.sendEmail({
53421
+ to,
53422
+ subject,
53423
+ text: bodies.text,
53424
+ ...bodies.html !== void 0 ? { html: bodies.html } : {},
53425
+ ...addonId !== void 0 ? { addonId } : {},
53426
+ ...from !== void 0 ? { from } : {}
53427
+ });
53428
+ return { success: true };
53429
+ } catch (err) {
53430
+ return {
53431
+ success: false,
53432
+ error: err instanceof Error ? err.message : String(err)
53433
+ };
53434
+ }
53435
+ }
53436
+ };
53437
+ }
53321
53438
  //#endregion
53322
53439
  //#region src/http.ts
53323
53440
  /** Default per-request timeout for notification dispatch. */
@@ -55253,7 +55370,8 @@ function zentikConfigSchema() {
55253
55370
  }
55254
55371
  //#endregion
55255
55372
  //#region src/adapters/registry.ts
55256
- var NOTIFIER_ADAPTERS = [
55373
+ /** HTTP adapters that need no runtime deps. */
55374
+ var HTTP_NOTIFIER_ADAPTERS = [
55257
55375
  ntfyAdapter,
55258
55376
  pushoverAdapter,
55259
55377
  gotifyAdapter,
@@ -55405,6 +55523,17 @@ var NOTIFIER_ADAPTERS = [
55405
55523
  },
55406
55524
  whatsappAdapter
55407
55525
  ];
55526
+ /**
55527
+ * Full catalog. When `smtp` is omitted the Email kind is still listed but
55528
+ * `sendDirect` fails until the addon wires a client (tests use this).
55529
+ */
55530
+ function buildNotifierAdapters(smtp) {
55531
+ const emailSmtp = smtp ?? { async sendEmail() {
55532
+ throw new Error("email: no smtp-provider client — install Google Services or SMTP Email under Connectivity → Email and Verify it");
55533
+ } };
55534
+ return [...HTTP_NOTIFIER_ADAPTERS, createEmailAdapter(emailSmtp)];
55535
+ }
55536
+ buildNotifierAdapters();
55408
55537
  var NOTIFIER_ICONS = {
55409
55538
  telegram: {
55410
55539
  contentType: "image/svg+xml",
@@ -56246,6 +56375,32 @@ function createNotificationOutputProvider(deps) {
56246
56375
  ...prepared,
56247
56376
  attachments
56248
56377
  };
56378
+ if (adapter.sendDirect) try {
56379
+ const result = await adapter.sendDirect(target.config, readyPrepared);
56380
+ return result.error === void 0 ? {
56381
+ ...result,
56382
+ renderedAs: prepared.renderedAs
56383
+ } : {
56384
+ ...result,
56385
+ error: stripUrls(result.error),
56386
+ renderedAs: prepared.renderedAs
56387
+ };
56388
+ } catch (err) {
56389
+ logger?.warn("sendDirect failed", { meta: {
56390
+ kind: target.kind,
56391
+ error: errMsg(err)
56392
+ } });
56393
+ return {
56394
+ success: false,
56395
+ error: `sendDirect failed: ${errMsg(err)}`,
56396
+ renderedAs: prepared.renderedAs
56397
+ };
56398
+ }
56399
+ if (!adapter.buildRequest) return {
56400
+ success: false,
56401
+ error: `No adapter send path for kind '${target.kind}'`,
56402
+ renderedAs: prepared.renderedAs
56403
+ };
56249
56404
  let plan;
56250
56405
  try {
56251
56406
  plan = adapter.buildRequest(target.config, readyPrepared);
@@ -56613,11 +56768,12 @@ var TargetStore = class {
56613
56768
  * Notifiers addon — a system `notification-output` collection provider.
56614
56769
  *
56615
56770
  * It hosts per-kind `NotifierAdapter`s (ntfy, pushover, gotify, telegram,
56616
- * discord, webhook, zentik, whatsapp) behind one uniform cap surface. Targets
56617
- * persist via the `settings-store` cap; the canonical notification is degraded
56618
- * per each target's declared capabilities by the pure `prepareNotification`
56619
- * engine in `@camstack/types`. The catalog is `NOTIFIER_ADAPTERS`
56620
- * (`adapters/registry.ts`).
56771
+ * discord, webhook, zentik, whatsapp, email) behind one uniform cap surface.
56772
+ * Targets persist via the `settings-store` cap; the canonical notification is
56773
+ * degraded per each target's declared capabilities by the pure
56774
+ * `prepareNotification` engine in `@camstack/types`. The catalog is
56775
+ * `buildNotifierAdapters` (`adapters/registry.ts`); email uses `sendDirect` →
56776
+ * `smtp-provider`.
56621
56777
  */
56622
56778
  var NotifiersAddon = class extends BaseAddon {
56623
56779
  constructor() {
@@ -56629,9 +56785,19 @@ var NotifiersAddon = class extends BaseAddon {
56629
56785
  if (!api) this.ctx.logger.warn("notifiers: no ctx.api — targets persist in-memory only");
56630
56786
  const store = new TargetStore(port);
56631
56787
  await store.init();
56788
+ const adapters = buildNotifierAdapters(api !== void 0 ? { sendEmail: async (input) => {
56789
+ return { messageId: (await api.smtpProvider.sendEmail({
56790
+ to: input.to,
56791
+ subject: input.subject,
56792
+ text: input.text,
56793
+ ...input.html !== void 0 ? { html: input.html } : {},
56794
+ ...input.from !== void 0 ? { from: input.from } : {},
56795
+ ...input.addonId !== void 0 ? { addonId: input.addonId } : {}
56796
+ })).messageId };
56797
+ } } : void 0);
56632
56798
  const provider = createNotificationOutputProvider({
56633
56799
  store,
56634
- adapters: NOTIFIER_ADAPTERS,
56800
+ adapters,
56635
56801
  logger: this.ctx.logger.child("notification-output"),
56636
56802
  iconUrlFor,
56637
56803
  iconMediaTypeFor
@@ -56641,7 +56807,7 @@ var NotifiersAddon = class extends BaseAddon {
56641
56807
  provider,
56642
56808
  logger: this.ctx.logger.child("nc-target-actions")
56643
56809
  });
56644
- this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: NOTIFIER_ADAPTERS.length } });
56810
+ this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: adapters.length } });
56645
56811
  return {
56646
56812
  providers: [{
56647
56813
  capability: notificationOutputCapability,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-notifiers",
3
- "version": "1.2.130",
3
+ "version": "1.2.131",
4
4
  "description": "System notifiers addon for CamStack — a `notification-output` collection provider hosting per-kind notifier adapters (ntfy, pushover, gotify, telegram, discord, webhook, zentik).",
5
5
  "keywords": [
6
6
  "camstack",