@oneuptime/common 10.5.4 → 10.5.7

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 (62) hide show
  1. package/Models/DatabaseModels/AlertEpisodeMember.ts +29 -0
  2. package/Models/DatabaseModels/GlobalConfig.ts +28 -0
  3. package/Models/DatabaseModels/IncidentEpisodeMember.ts +29 -0
  4. package/Models/DatabaseModels/ProjectSmtpConfig.ts +62 -6
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1779975064262-AddTransportTypeToProjectSmtpConfig.ts +37 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1779976190561-AddSmtpTransportTypeToGlobalConfig.ts +19 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1779980428744-MigrationName.ts +23 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  9. package/Server/Services/AlertEpisodeMemberService.ts +23 -0
  10. package/Server/Services/IncidentEpisodeMemberService.ts +23 -0
  11. package/Server/Services/MailService.ts +17 -3
  12. package/Server/Services/ProjectSmtpConfigService.ts +30 -10
  13. package/Server/Services/StatusPagePrivateUserService.ts +1 -0
  14. package/Server/Services/StatusPageSubscriberService.ts +3 -0
  15. package/Server/Services/UserNotificationSettingService.ts +12 -0
  16. package/Server/Utils/WhatsAppTemplateUtil.ts +4 -0
  17. package/Types/Email/EmailServer.ts +22 -4
  18. package/Types/Email/EmailTemplateType.ts +2 -0
  19. package/Types/Email/MailTransportType.ts +18 -0
  20. package/Types/NotificationSetting/NotificationSettingEventType.ts +2 -0
  21. package/Types/WhatsApp/WhatsAppTemplates.ts +10 -0
  22. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +30 -0
  23. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
  24. package/build/dist/Models/DatabaseModels/GlobalConfig.js +28 -0
  25. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  26. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +30 -0
  27. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
  28. package/build/dist/Models/DatabaseModels/ProjectSmtpConfig.js +59 -6
  29. package/build/dist/Models/DatabaseModels/ProjectSmtpConfig.js.map +1 -1
  30. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779975064262-AddTransportTypeToProjectSmtpConfig.js +18 -0
  31. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779975064262-AddTransportTypeToProjectSmtpConfig.js.map +1 -0
  32. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779976190561-AddSmtpTransportTypeToGlobalConfig.js +12 -0
  33. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779976190561-AddSmtpTransportTypeToGlobalConfig.js.map +1 -0
  34. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779980428744-MigrationName.js +14 -0
  35. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779980428744-MigrationName.js.map +1 -0
  36. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  37. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  38. package/build/dist/Server/Services/AlertEpisodeMemberService.js +20 -0
  39. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  40. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +20 -0
  41. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  42. package/build/dist/Server/Services/MailService.js +17 -3
  43. package/build/dist/Server/Services/MailService.js.map +1 -1
  44. package/build/dist/Server/Services/ProjectSmtpConfigService.js +24 -8
  45. package/build/dist/Server/Services/ProjectSmtpConfigService.js.map +1 -1
  46. package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -0
  47. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  48. package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
  49. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  50. package/build/dist/Server/Services/UserNotificationSettingService.js +2 -0
  51. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  52. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js +2 -0
  53. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js.map +1 -1
  54. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  55. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  56. package/build/dist/Types/Email/MailTransportType.js +19 -0
  57. package/build/dist/Types/Email/MailTransportType.js.map +1 -0
  58. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +2 -0
  59. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  60. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js +6 -0
  61. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js.map +1 -1
  62. package/package.json +1 -1
@@ -626,6 +626,35 @@ export default class AlertEpisodeMember extends BaseModel {
626
626
  })
627
627
  public createdByUserId?: ObjectID = undefined;
628
628
 
629
+ @ColumnAccessControl({
630
+ create: [],
631
+ read: [
632
+ Permission.ProjectOwner,
633
+ Permission.ProjectAdmin,
634
+ Permission.ProjectMember,
635
+ Permission.Viewer,
636
+ Permission.AlertAdmin,
637
+ Permission.AlertMember,
638
+ Permission.AlertViewer,
639
+ Permission.ReadAlertEpisodeMember,
640
+ ],
641
+ update: [],
642
+ })
643
+ @TableColumn({
644
+ isDefaultValueColumn: true,
645
+ required: true,
646
+ type: TableColumnType.Boolean,
647
+ title: "Is Owner Notified of Alert Added to Episode",
648
+ description:
649
+ "Has the owner been notified that this alert was added to the episode?",
650
+ })
651
+ @Column({
652
+ type: ColumnType.Boolean,
653
+ nullable: false,
654
+ default: false,
655
+ })
656
+ public isOwnerNotifiedOfAlertAdded?: boolean = undefined;
657
+
629
658
  @ColumnAccessControl({
630
659
  create: [],
631
660
  read: [
@@ -10,6 +10,7 @@ import TableColumn from "../../Types/Database/TableColumn";
10
10
  import TableColumnType from "../../Types/Database/TableColumnType";
11
11
  import TableMetadata from "../../Types/Database/TableMetadata";
12
12
  import Email from "../../Types/Email";
13
+ import MailTransportType from "../../Types/Email/MailTransportType";
13
14
  import OAuthProviderType from "../../Types/Email/OAuthProviderType";
14
15
  import SMTPAuthenticationType from "../../Types/Email/SMTPAuthenticationType";
15
16
  import IconProp from "../../Types/Icon/IconProp";
@@ -208,6 +209,33 @@ export default class GlobalConfig extends GlobalConfigModel {
208
209
  })
209
210
  public smtpFromName?: string = undefined;
210
211
 
212
+ /*
213
+ * Transport selection for the global SMTP. Defaults to SMTP for backwards
214
+ * compatibility. 'Microsoft Graph' bypasses SMTP entirely and posts to
215
+ * https://graph.microsoft.com/v1.0/users/{from}/sendMail — useful for tenants
216
+ * that have SMTP AUTH disabled.
217
+ */
218
+
219
+ @ColumnAccessControl({
220
+ create: [],
221
+ read: [],
222
+ update: [],
223
+ })
224
+ @TableColumn({
225
+ type: TableColumnType.ShortText,
226
+ title: "SMTP Transport",
227
+ description:
228
+ "How OneUptime delivers mail using the global SMTP config. 'SMTP' uses the host/port. 'Microsoft Graph' sends via the Microsoft Graph REST API.",
229
+ defaultValue: MailTransportType.SMTP,
230
+ })
231
+ @Column({
232
+ type: ColumnType.ShortText,
233
+ length: ColumnLength.ShortText,
234
+ nullable: true,
235
+ default: MailTransportType.SMTP,
236
+ })
237
+ public smtpTransportType?: MailTransportType = undefined;
238
+
211
239
  // SMTP OAuth 2.0 Settings.
212
240
 
213
241
  @ColumnAccessControl({
@@ -627,6 +627,35 @@ export default class IncidentEpisodeMember extends BaseModel {
627
627
  })
628
628
  public createdByUserId?: ObjectID = undefined;
629
629
 
630
+ @ColumnAccessControl({
631
+ create: [],
632
+ read: [
633
+ Permission.ProjectOwner,
634
+ Permission.ProjectAdmin,
635
+ Permission.ProjectMember,
636
+ Permission.Viewer,
637
+ Permission.IncidentAdmin,
638
+ Permission.IncidentMember,
639
+ Permission.IncidentViewer,
640
+ Permission.ReadIncidentEpisodeMember,
641
+ ],
642
+ update: [],
643
+ })
644
+ @TableColumn({
645
+ isDefaultValueColumn: true,
646
+ required: true,
647
+ type: TableColumnType.Boolean,
648
+ title: "Is Owner Notified of Incident Added to Episode",
649
+ description:
650
+ "Has the owner been notified that this incident was added to the episode?",
651
+ })
652
+ @Column({
653
+ type: ColumnType.Boolean,
654
+ nullable: false,
655
+ default: false,
656
+ })
657
+ public isOwnerNotifiedOfIncidentAdded?: boolean = undefined;
658
+
630
659
  @ColumnAccessControl({
631
660
  create: [],
632
661
  read: [
@@ -17,6 +17,7 @@ import TableMetadata from "../../Types/Database/TableMetadata";
17
17
  import TenantColumn from "../../Types/Database/TenantColumn";
18
18
  import UniqueColumnBy from "../../Types/Database/UniqueColumnBy";
19
19
  import Email from "../../Types/Email";
20
+ import MailTransportType from "../../Types/Email/MailTransportType";
20
21
  import OAuthProviderType from "../../Types/Email/OAuthProviderType";
21
22
  import SMTPAuthenticationType from "../../Types/Email/SMTPAuthenticationType";
22
23
  import IconProp from "../../Types/Icon/IconProp";
@@ -478,12 +479,15 @@ export default class ProjectSmtpConfig extends BaseModel {
478
479
  ],
479
480
  })
480
481
  @TableColumn({
481
- required: true,
482
+ required: false,
482
483
  type: TableColumnType.ShortText,
484
+ title: "Hostname",
485
+ description:
486
+ "SMTP server hostname. Required when Transport is SMTP. Not used by HTTP-API transports like Microsoft Graph.",
483
487
  example: "smtp.gmail.com",
484
488
  })
485
489
  @Column({
486
- nullable: false,
490
+ nullable: true,
487
491
  type: ColumnType.ShortText,
488
492
  length: ColumnLength.ShortText,
489
493
  transformer: Hostname.getDatabaseTransformer(),
@@ -513,12 +517,15 @@ export default class ProjectSmtpConfig extends BaseModel {
513
517
  ],
514
518
  })
515
519
  @TableColumn({
516
- required: true,
520
+ required: false,
517
521
  type: TableColumnType.Number,
522
+ title: "Port",
523
+ description:
524
+ "SMTP server port. Required when Transport is SMTP. Not used by HTTP-API transports.",
518
525
  example: 587,
519
526
  })
520
527
  @Column({
521
- nullable: false,
528
+ nullable: true,
522
529
  type: ColumnType.Number,
523
530
  transformer: Port.getDatabaseTransformer(),
524
531
  })
@@ -616,18 +623,67 @@ export default class ProjectSmtpConfig extends BaseModel {
616
623
  ],
617
624
  })
618
625
  @TableColumn({
619
- required: true,
626
+ required: false,
620
627
  type: TableColumnType.Boolean,
628
+ title: "Use SSL / TLS",
629
+ description:
630
+ "Enable secure SMTP connection. Used only for SMTP transport — HTTP-API transports always use HTTPS.",
621
631
  defaultValue: true,
622
632
  example: true,
623
633
  })
624
634
  @Column({
625
- nullable: false,
635
+ nullable: true,
626
636
  type: ColumnType.Boolean,
627
637
  default: true,
628
638
  })
629
639
  public secure?: boolean = undefined;
630
640
 
641
+ /*
642
+ * Transport type selects how mail is actually delivered.
643
+ * SMTP (default) uses the host/port above. HTTP-API transports (e.g. Microsoft
644
+ * Graph) ignore host/port and call the provider's REST API directly using the
645
+ * OAuth credentials below.
646
+ */
647
+
648
+ @ColumnAccessControl({
649
+ create: [
650
+ Permission.ProjectOwner,
651
+ Permission.ProjectAdmin,
652
+ Permission.CreateProjectSMTPConfig,
653
+ ],
654
+ read: [
655
+ Permission.ProjectOwner,
656
+ Permission.ProjectAdmin,
657
+ Permission.ProjectMember,
658
+ Permission.Viewer,
659
+ Permission.SettingsAdmin,
660
+ Permission.SettingsMember,
661
+ Permission.SettingsViewer,
662
+ Permission.ReadProjectSMTPConfig,
663
+ ],
664
+ update: [
665
+ Permission.ProjectOwner,
666
+ Permission.ProjectAdmin,
667
+ Permission.EditProjectSMTPConfig,
668
+ ],
669
+ })
670
+ @TableColumn({
671
+ required: true,
672
+ type: TableColumnType.ShortText,
673
+ title: "Transport",
674
+ description:
675
+ "How OneUptime delivers mail for this config. 'SMTP' uses the hostname/port. 'Microsoft Graph' sends via the Microsoft Graph REST API — use this when your Microsoft 365 tenant has SMTP AUTH disabled.",
676
+ defaultValue: MailTransportType.SMTP,
677
+ example: "SMTP",
678
+ })
679
+ @Column({
680
+ nullable: false,
681
+ type: ColumnType.ShortText,
682
+ length: ColumnLength.ShortText,
683
+ default: MailTransportType.SMTP,
684
+ })
685
+ public transportType?: MailTransportType = undefined;
686
+
631
687
  // OAuth 2.0 Configuration Fields
632
688
 
633
689
  @ColumnAccessControl({
@@ -0,0 +1,37 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddTransportTypeToProjectSmtpConfig1779975064262
4
+ implements MigrationInterface
5
+ {
6
+ public name: string = "AddTransportTypeToProjectSmtpConfig1779975064262";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "ProjectSMTPConfig" ADD "transportType" character varying(100) NOT NULL DEFAULT 'SMTP'`,
11
+ );
12
+ await queryRunner.query(
13
+ `ALTER TABLE "ProjectSMTPConfig" ALTER COLUMN "hostname" DROP NOT NULL`,
14
+ );
15
+ await queryRunner.query(
16
+ `ALTER TABLE "ProjectSMTPConfig" ALTER COLUMN "port" DROP NOT NULL`,
17
+ );
18
+ await queryRunner.query(
19
+ `ALTER TABLE "ProjectSMTPConfig" ALTER COLUMN "secure" DROP NOT NULL`,
20
+ );
21
+ }
22
+
23
+ public async down(queryRunner: QueryRunner): Promise<void> {
24
+ await queryRunner.query(
25
+ `ALTER TABLE "ProjectSMTPConfig" ALTER COLUMN "secure" SET NOT NULL`,
26
+ );
27
+ await queryRunner.query(
28
+ `ALTER TABLE "ProjectSMTPConfig" ALTER COLUMN "port" SET NOT NULL`,
29
+ );
30
+ await queryRunner.query(
31
+ `ALTER TABLE "ProjectSMTPConfig" ALTER COLUMN "hostname" SET NOT NULL`,
32
+ );
33
+ await queryRunner.query(
34
+ `ALTER TABLE "ProjectSMTPConfig" DROP COLUMN "transportType"`,
35
+ );
36
+ }
37
+ }
@@ -0,0 +1,19 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddSmtpTransportTypeToGlobalConfig1779976190561
4
+ implements MigrationInterface
5
+ {
6
+ public name: string = "AddSmtpTransportTypeToGlobalConfig1779976190561";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "GlobalConfig" ADD "smtpTransportType" character varying(100) DEFAULT 'SMTP'`,
11
+ );
12
+ }
13
+
14
+ public async down(queryRunner: QueryRunner): Promise<void> {
15
+ await queryRunner.query(
16
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "smtpTransportType"`,
17
+ );
18
+ }
19
+ }
@@ -0,0 +1,23 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1779980428744 implements MigrationInterface {
4
+ public name = "MigrationName1779980428744";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "AlertEpisodeMember" ADD "isOwnerNotifiedOfAlertAdded" boolean NOT NULL DEFAULT false`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "IncidentEpisodeMember" ADD "isOwnerNotifiedOfIncidentAdded" boolean NOT NULL DEFAULT false`,
12
+ );
13
+ }
14
+
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
16
+ await queryRunner.query(
17
+ `ALTER TABLE "IncidentEpisodeMember" DROP COLUMN "isOwnerNotifiedOfIncidentAdded"`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "AlertEpisodeMember" DROP COLUMN "isOwnerNotifiedOfAlertAdded"`,
21
+ );
22
+ }
23
+ }
@@ -357,6 +357,9 @@ import { RenameStatusPageZhToZhCN1779827700000 } from "./1779827700000-RenameSta
357
357
  import { MigrationName1779879993421 } from "./1779879993421-MigrationName";
358
358
  import { MigrationName1779882573463 } from "./1779882573463-MigrationName";
359
359
  import { AddLabelGroupByToGroupingRules1779971548393 } from "./1779971548393-AddLabelGroupByToGroupingRules";
360
+ import { AddTransportTypeToProjectSmtpConfig1779975064262 } from "./1779975064262-AddTransportTypeToProjectSmtpConfig";
361
+ import { AddSmtpTransportTypeToGlobalConfig1779976190561 } from "./1779976190561-AddSmtpTransportTypeToGlobalConfig";
362
+ import { MigrationName1779980428744 } from "./1779980428744-MigrationName";
360
363
 
361
364
  export default [
362
365
  InitialMigration,
@@ -718,4 +721,7 @@ export default [
718
721
  MigrationName1779879993421,
719
722
  MigrationName1779882573463,
720
723
  AddLabelGroupByToGroupingRules1779971548393,
724
+ AddTransportTypeToProjectSmtpConfig1779975064262,
725
+ AddSmtpTransportTypeToGlobalConfig1779976190561,
726
+ MigrationName1779980428744,
721
727
  ];
@@ -4,6 +4,7 @@ import { OnCreate, OnDelete } from "../Types/Database/Hooks";
4
4
  import DatabaseService from "./DatabaseService";
5
5
  import BadDataException from "../../Types/Exception/BadDataException";
6
6
  import ObjectID from "../../Types/ObjectID";
7
+ import PositiveNumber from "../../Types/PositiveNumber";
7
8
  import Model from "../../Models/DatabaseModels/AlertEpisodeMember";
8
9
  import Alert from "../../Models/DatabaseModels/Alert";
9
10
  import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
@@ -62,6 +63,28 @@ export class Service extends DatabaseService<Model> {
62
63
  createBy.data.addedAt = OneUptimeDate.getCurrentDate();
63
64
  }
64
65
 
66
+ /*
67
+ * If this is the very first alert in the episode (the founder), the
68
+ * "episode created" notification already covers it. Mark the member as
69
+ * "already notified" so the AlertAdded-to-episode cron skips it. This
70
+ * avoids double-notifying owners when an episode is born with its first
71
+ * alert.
72
+ */
73
+ if (createBy.data.isOwnerNotifiedOfAlertAdded === undefined) {
74
+ const existingMemberCount: PositiveNumber = await this.countBy({
75
+ query: {
76
+ alertEpisodeId: createBy.data.alertEpisodeId,
77
+ },
78
+ props: {
79
+ isRoot: true,
80
+ },
81
+ });
82
+
83
+ if (existingMemberCount.toNumber() === 0) {
84
+ createBy.data.isOwnerNotifiedOfAlertAdded = true;
85
+ }
86
+ }
87
+
65
88
  return { createBy, carryForward: null };
66
89
  }
67
90
 
@@ -4,6 +4,7 @@ import { OnCreate, OnDelete } from "../Types/Database/Hooks";
4
4
  import DatabaseService from "./DatabaseService";
5
5
  import BadDataException from "../../Types/Exception/BadDataException";
6
6
  import ObjectID from "../../Types/ObjectID";
7
+ import PositiveNumber from "../../Types/PositiveNumber";
7
8
  import Model from "../../Models/DatabaseModels/IncidentEpisodeMember";
8
9
  import Incident from "../../Models/DatabaseModels/Incident";
9
10
  import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
@@ -64,6 +65,28 @@ export class Service extends DatabaseService<Model> {
64
65
  createBy.data.addedAt = OneUptimeDate.getCurrentDate();
65
66
  }
66
67
 
68
+ /*
69
+ * If this is the very first incident in the episode (the founder), the
70
+ * "episode created" notification already covers it. Mark the member as
71
+ * "already notified" so the IncidentAdded-to-episode cron skips it. This
72
+ * avoids double-notifying owners when an episode is born with its first
73
+ * incident.
74
+ */
75
+ if (createBy.data.isOwnerNotifiedOfIncidentAdded === undefined) {
76
+ const existingMemberCount: PositiveNumber = await this.countBy({
77
+ query: {
78
+ incidentEpisodeId: createBy.data.incidentEpisodeId,
79
+ },
80
+ props: {
81
+ isRoot: true,
82
+ },
83
+ });
84
+
85
+ if (existingMemberCount.toNumber() === 0) {
86
+ createBy.data.isOwnerNotifiedOfIncidentAdded = true;
87
+ }
88
+ }
89
+
67
90
  return { createBy, carryForward: null };
68
91
  }
69
92
 
@@ -45,12 +45,26 @@ export class MailService extends BaseService {
45
45
 
46
46
  if (options && options.mailServer) {
47
47
  body["SMTP_ID"] = options.mailServer.id?.toString();
48
+ /*
49
+ * host/port/secure are optional on EmailServer because HTTP-API
50
+ * transports (e.g. Microsoft Graph) don't use them. Only serialize the
51
+ * SMTP-only fields when they're actually present.
52
+ */
53
+ body["SMTP_TRANSPORT_TYPE"] =
54
+ options.mailServer.transportType || undefined;
48
55
  body["SMTP_USERNAME"] = options.mailServer.username || undefined;
49
56
  body["SMTP_EMAIL"] = options.mailServer.fromEmail.toString();
50
57
  body["SMTP_FROM_NAME"] = options.mailServer.fromName;
51
- body["SMTP_IS_SECURE"] = options.mailServer.secure;
52
- body["SMTP_PORT"] = options.mailServer.port.toNumber();
53
- body["SMTP_HOST"] = options.mailServer.host.toString();
58
+ body["SMTP_IS_SECURE"] =
59
+ options.mailServer.secure === undefined
60
+ ? undefined
61
+ : options.mailServer.secure;
62
+ body["SMTP_PORT"] = options.mailServer.port
63
+ ? options.mailServer.port.toNumber()
64
+ : undefined;
65
+ body["SMTP_HOST"] = options.mailServer.host
66
+ ? options.mailServer.host.toString()
67
+ : undefined;
54
68
  body["SMTP_PASSWORD"] = options.mailServer.password || undefined;
55
69
  body["SMTP_AUTH_TYPE"] = options.mailServer.authType || undefined;
56
70
  body["SMTP_CLIENT_ID"] = options.mailServer.clientId || undefined;
@@ -1,5 +1,6 @@
1
1
  import DatabaseService from "./DatabaseService";
2
2
  import EmailServer from "../../Types/Email/EmailServer";
3
+ import MailTransportType from "../../Types/Email/MailTransportType";
3
4
  import SMTPAuthenticationType from "../../Types/Email/SMTPAuthenticationType";
4
5
  import BadDataException from "../../Types/Exception/BadDataException";
5
6
  import URL from "../../Types/API/URL";
@@ -21,17 +22,28 @@ export class Service extends DatabaseService<Model> {
21
22
  throw new BadDataException("Project SMTP config id is not set");
22
23
  }
23
24
 
24
- if (!projectSmtpConfig.hostname) {
25
- throw new BadDataException("Project SMTP config host is not set");
26
- }
27
-
28
- if (!projectSmtpConfig.port) {
29
- throw new BadDataException("Project SMTP config port is not set");
30
- }
25
+ const transportType: MailTransportType =
26
+ projectSmtpConfig.transportType || MailTransportType.SMTP;
31
27
 
32
- // Get auth type, default to UsernamePassword for backward compatibility
28
+ /*
29
+ * Get auth type, default to UsernamePassword for backward compatibility.
30
+ * Microsoft Graph always uses OAuth (Client Credentials) regardless of what
31
+ * the user picked — but we still let the existing OAuth validation apply.
32
+ */
33
33
  const authType: SMTPAuthenticationType =
34
- projectSmtpConfig.authType || SMTPAuthenticationType.UsernamePassword;
34
+ transportType === MailTransportType.MicrosoftGraph
35
+ ? SMTPAuthenticationType.OAuth
36
+ : projectSmtpConfig.authType || SMTPAuthenticationType.UsernamePassword;
37
+
38
+ if (transportType === MailTransportType.SMTP) {
39
+ if (!projectSmtpConfig.hostname) {
40
+ throw new BadDataException("Project SMTP config host is not set");
41
+ }
42
+
43
+ if (!projectSmtpConfig.port) {
44
+ throw new BadDataException("Project SMTP config port is not set");
45
+ }
46
+ }
35
47
 
36
48
  // Validate based on auth type
37
49
  if (authType === SMTPAuthenticationType.UsernamePassword) {
@@ -43,7 +55,14 @@ export class Service extends DatabaseService<Model> {
43
55
  throw new BadDataException("Project SMTP config password is not set");
44
56
  }
45
57
  } else if (authType === SMTPAuthenticationType.OAuth) {
46
- if (!projectSmtpConfig.username) {
58
+ /*
59
+ * For Microsoft Graph, username is optional — we fall back to fromEmail
60
+ * as the sender mailbox. For SMTP+XOAUTH2, username is required.
61
+ */
62
+ if (
63
+ transportType === MailTransportType.SMTP &&
64
+ !projectSmtpConfig.username
65
+ ) {
47
66
  throw new BadDataException(
48
67
  "Project SMTP config username (email address) is not set for OAuth",
49
68
  );
@@ -85,6 +104,7 @@ export class Service extends DatabaseService<Model> {
85
104
 
86
105
  return {
87
106
  id: projectSmtpConfig.id!,
107
+ transportType: transportType,
88
108
  host: projectSmtpConfig.hostname,
89
109
  port: projectSmtpConfig.port,
90
110
  username: projectSmtpConfig.username,
@@ -92,6 +92,7 @@ export class Service extends DatabaseService<Model> {
92
92
  projectId: true,
93
93
  smtpConfig: {
94
94
  _id: true,
95
+ transportType: true,
95
96
  hostname: true,
96
97
  port: true,
97
98
  username: true,
@@ -684,6 +684,7 @@ Stay informed about service availability! 🚀`;
684
684
  name: true,
685
685
  smtpConfig: {
686
686
  _id: true,
687
+ transportType: true,
687
688
  hostname: true,
688
689
  port: true,
689
690
  username: true,
@@ -915,6 +916,7 @@ Stay informed about service availability! 🚀`;
915
916
  name: true,
916
917
  smtpConfig: {
917
918
  _id: true,
919
+ transportType: true,
918
920
  hostname: true,
919
921
  port: true,
920
922
  username: true,
@@ -1347,6 +1349,7 @@ Stay informed about service availability! 🚀`;
1347
1349
  allowSubscribersToChooseEventTypes: true,
1348
1350
  smtpConfig: {
1349
1351
  _id: true,
1352
+ transportType: true,
1350
1353
  hostname: true,
1351
1354
  port: true,
1352
1355
  username: true,
@@ -706,6 +706,12 @@ export class Service extends DatabaseService<UserNotificationSetting> {
706
706
  projectId,
707
707
  NotificationSettingEventType.SEND_ALERT_EPISODE_STATE_CHANGED_OWNER_NOTIFICATION,
708
708
  );
709
+
710
+ await this.addNotificationSettingIfNotExists(
711
+ userId,
712
+ projectId,
713
+ NotificationSettingEventType.SEND_ALERT_ADDED_TO_EPISODE_OWNER_NOTIFICATION,
714
+ );
709
715
  }
710
716
 
711
717
  private async addIncidentEpisodeNotificationSettings(
@@ -723,6 +729,12 @@ export class Service extends DatabaseService<UserNotificationSetting> {
723
729
  projectId,
724
730
  NotificationSettingEventType.SEND_INCIDENT_EPISODE_STATE_CHANGED_OWNER_NOTIFICATION,
725
731
  );
732
+
733
+ await this.addNotificationSettingIfNotExists(
734
+ userId,
735
+ projectId,
736
+ NotificationSettingEventType.SEND_INCIDENT_ADDED_TO_EPISODE_OWNER_NOTIFICATION,
737
+ );
726
738
  }
727
739
 
728
740
  private async addNotificationSettingIfNotExists(
@@ -94,6 +94,8 @@ const templateIdByEventType: Record<
94
94
  WhatsAppTemplateIds.AlertEpisodeStateChangedOwnerNotification,
95
95
  [NotificationSettingEventType.SEND_ALERT_EPISODE_OWNER_ADDED_NOTIFICATION]:
96
96
  WhatsAppTemplateIds.AlertEpisodeOwnerAddedNotification,
97
+ [NotificationSettingEventType.SEND_ALERT_ADDED_TO_EPISODE_OWNER_NOTIFICATION]:
98
+ WhatsAppTemplateIds.AlertAddedToEpisodeOwnerNotification,
97
99
  [NotificationSettingEventType.SEND_INCIDENT_EPISODE_CREATED_OWNER_NOTIFICATION]:
98
100
  WhatsAppTemplateIds.IncidentEpisodeCreatedOwnerNotification,
99
101
  [NotificationSettingEventType.SEND_INCIDENT_EPISODE_NOTE_POSTED_OWNER_NOTIFICATION]:
@@ -102,6 +104,8 @@ const templateIdByEventType: Record<
102
104
  WhatsAppTemplateIds.IncidentEpisodeStateChangedOwnerNotification,
103
105
  [NotificationSettingEventType.SEND_INCIDENT_EPISODE_OWNER_ADDED_NOTIFICATION]:
104
106
  WhatsAppTemplateIds.IncidentEpisodeOwnerAddedNotification,
107
+ [NotificationSettingEventType.SEND_INCIDENT_ADDED_TO_EPISODE_OWNER_NOTIFICATION]:
108
+ WhatsAppTemplateIds.IncidentAddedToEpisodeOwnerNotification,
105
109
  [NotificationSettingEventType.SEND_MONITOR_OWNER_ADDED_NOTIFICATION]:
106
110
  WhatsAppTemplateIds.MonitorOwnerAddedNotification,
107
111
  [NotificationSettingEventType.SEND_MONITOR_CREATED_OWNER_NOTIFICATION]:
@@ -3,20 +3,38 @@ import URL from "../API/URL";
3
3
  import Email from "../Email";
4
4
  import ObjectID from "../ObjectID";
5
5
  import Port from "../Port";
6
+ import MailTransportType from "./MailTransportType";
6
7
  import OAuthProviderType from "./OAuthProviderType";
7
8
  import SMTPAuthenticationType from "./SMTPAuthenticationType";
8
9
 
9
10
  export default interface EmailServer {
10
11
  id?: ObjectID | undefined; // If this is custom SMTP, this is the ID of the SMTP config. Otherwise, it's undefined
11
- host: Hostname;
12
- port: Port;
12
+
13
+ /*
14
+ * Transport selection.
15
+ * Defaults to SMTP when undefined for backwards compatibility with existing
16
+ * EmailServer constructions.
17
+ */
18
+ transportType?: MailTransportType | undefined;
19
+
20
+ /*
21
+ * SMTP transport fields. Required when transportType === SMTP (or undefined).
22
+ * Not used for HTTP-API transports like MicrosoftGraph.
23
+ */
24
+ host?: Hostname | undefined;
25
+ port?: Port | undefined;
26
+ secure?: boolean | undefined;
27
+
13
28
  username: string | undefined;
14
29
  password: string | undefined;
15
- secure: boolean;
16
30
  fromEmail: Email;
17
31
  fromName: string;
18
32
 
19
- // OAuth 2.0 fields for any OAuth-enabled SMTP server
33
+ /*
34
+ * OAuth 2.0 fields. Used by:
35
+ * - SMTP transport when authType === OAuth (XOAUTH2)
36
+ * - HTTP-API transports (always — they always authenticate via OAuth2)
37
+ */
20
38
  authType?: SMTPAuthenticationType | undefined;
21
39
  clientId?: string | undefined; // OAuth Application Client ID
22
40
  clientSecret?: string | undefined; // OAuth Application Client Secret
@@ -47,11 +47,13 @@ enum EmailTemplateType {
47
47
  AlertEpisodeOwnerStateChanged = "AlertEpisodeOwnerStateChanged.hbs",
48
48
  AlertEpisodeOwnerNotePosted = "AlertEpisodeOwnerNotePosted.hbs",
49
49
  AlertEpisodeOwnerResourceCreated = "AlertEpisodeOwnerResourceCreated.hbs",
50
+ AlertEpisodeOwnerAlertAdded = "AlertEpisodeOwnerAlertAdded.hbs",
50
51
 
51
52
  IncidentEpisodeOwnerAdded = "IncidentEpisodeOwnerAdded.hbs",
52
53
  IncidentEpisodeOwnerStateChanged = "IncidentEpisodeOwnerStateChanged.hbs",
53
54
  IncidentEpisodeOwnerNotePosted = "IncidentEpisodeOwnerNotePosted.hbs",
54
55
  IncidentEpisodeOwnerResourceCreated = "IncidentEpisodeOwnerResourceCreated.hbs",
56
+ IncidentEpisodeOwnerIncidentAdded = "IncidentEpisodeOwnerIncidentAdded.hbs",
55
57
 
56
58
  SubscriberEpisodeCreated = "SubscriberEpisodeCreated.hbs",
57
59
  SubscriberEpisodeStateChanged = "SubscriberEpisodeStateChanged.hbs",
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Transport type for delivering email.
3
+ *
4
+ * - SMTP: traditional SMTP server (with optional XOAUTH2 / username+password / no auth)
5
+ * - MicrosoftGraph: Microsoft 365 mailbox via Microsoft Graph API
6
+ * (POST /v1.0/users/{sender}/sendMail). Required when the tenant has SMTP AUTH
7
+ * disabled — the Azure AD app only needs Mail.Send (application) permission.
8
+ *
9
+ * Adding a new HTTP-API provider (Gmail, SES, etc.) means adding a new enum value
10
+ * and a matching MailProvider implementation. See
11
+ * App/FeatureSet/Notification/Services/MailProviders.
12
+ */
13
+ enum MailTransportType {
14
+ SMTP = "SMTP",
15
+ MicrosoftGraph = "Microsoft Graph",
16
+ }
17
+
18
+ export default MailTransportType;