diff --git a/backend/apps/cloud/src/common/templates/en/subscribe-reminder.html b/backend/apps/cloud/src/common/templates/en/subscribe-reminder.html new file mode 100644 index 000000000..c0e18de61 --- /dev/null +++ b/backend/apps/cloud/src/common/templates/en/subscribe-reminder.html @@ -0,0 +1,22 @@ +Hey there,

+ +You signed up for Swetrix a couple of days ago and set up your project — nice! +But it looks like you haven't started your free trial yet.

+ +The trial is 14 days, no credit card required, and gives you full access to +everything Swetrix offers — unlimited features, no restrictions.

+ +Start your free trial

+ +Here's what you get:
+ + +If you have any questions or need help, just reply to this email.

+ +Andrii Romasiun
+Founder, Swetrix diff --git a/backend/apps/cloud/src/mailer/letter.ts b/backend/apps/cloud/src/mailer/letter.ts index d7c8cafe1..ce1e69f42 100644 --- a/backend/apps/cloud/src/mailer/letter.ts +++ b/backend/apps/cloud/src/mailer/letter.ts @@ -23,4 +23,5 @@ export enum LetterTemplate { OrganisationInvitationUnregistered = 'organisation-invitation-unregistered', SocialIdentityLinked = 'social-identity-linked', NoEventsAfterSignup = 'no-events-after-signup', + SubscribeReminder = 'subscribe-reminder', } diff --git a/backend/apps/cloud/src/mailer/mailer.service.ts b/backend/apps/cloud/src/mailer/mailer.service.ts index fd8824528..05d31f53a 100644 --- a/backend/apps/cloud/src/mailer/mailer.service.ts +++ b/backend/apps/cloud/src/mailer/mailer.service.ts @@ -134,6 +134,11 @@ const metaInfoJson = { en: () => 'Need help setting up Swetrix?', }, }, + [LetterTemplate.SubscribeReminder]: { + subject: { + en: () => 'Your Swetrix trial is waiting for you', + }, + }, } interface Params { diff --git a/backend/apps/cloud/src/task-manager/task-manager.service.ts b/backend/apps/cloud/src/task-manager/task-manager.service.ts index e221ca556..47c8e0e88 100644 --- a/backend/apps/cloud/src/task-manager/task-manager.service.ts +++ b/backend/apps/cloud/src/task-manager/task-manager.service.ts @@ -1114,7 +1114,7 @@ export class TaskManagerService { const users = await this.userService.find({ where: { isActive: true, - // we don't want to send the reminder for people who for example used it long time ago, then removed their projects and now are kinda counted as "new users" + planCode: Not(PlanCode.none), created: Between(weekAgo, twoDaysAgo), noEventsReminderSentOn: IsNull(), }, @@ -1163,6 +1163,50 @@ export class TaskManagerService { }) } + @Cron(CronExpression.EVERY_DAY_AT_10AM) + async remindUsersToSubscribe() { + const weekAgo = dayjs.utc().subtract(1, 'week').toDate() + const twoDaysAgo = dayjs + .utc() + .subtract(NO_EVENTS_REMINDER_DELAY_DAYS, 'days') + .toDate() + + const users = await this.userService.find({ + where: { + isActive: true, + planCode: PlanCode.none, + hasCompletedOnboarding: true, + created: Between(weekAgo, twoDaysAgo), + subscribeReminderSentOn: IsNull(), + }, + select: ['id', 'email'], + }) + + if (_isEmpty(users)) { + return + } + + const subscribeUrl = `${this.configService.get('CLIENT_URL')}/subscribe` + const sentOn = dayjs.utc().format('YYYY-MM-DD HH:mm:ss') + + await mapLimit(users, REPORTS_USERS_CONCURRENCY, async (user) => { + try { + await this.mailerService.sendEmail( + user.email, + LetterTemplate.SubscribeReminder, + { subscribeUrl }, + ) + await this.userService.update(user.id, { + subscribeReminderSentOn: sentOn, + }) + } catch (reason) { + this.logger.error( + `[CRON WORKER](remindUsersToSubscribe) Failed to process user ${user.id}: ${reason}`, + ) + } + }) + } + @Cron(CronExpression.EVERY_2_HOURS) async deleteOldShareInvitations() { const minDate = dayjs.utc().subtract(PROJECT_INVITE_EXPIRE, 'h').toDate() diff --git a/backend/apps/cloud/src/user/entities/user.entity.ts b/backend/apps/cloud/src/user/entities/user.entity.ts index 4d3a4d9d1..1dda90654 100644 --- a/backend/apps/cloud/src/user/entities/user.entity.ts +++ b/backend/apps/cloud/src/user/entities/user.entity.ts @@ -274,6 +274,10 @@ export class User { @Column({ type: 'timestamp', nullable: true }) noEventsReminderSentOn: Date + // the date when the "you haven't subscribed yet" reminder email was sent + @Column({ type: 'timestamp', nullable: true }) + subscribeReminderSentOn: Date + @Column('varchar', { length: 15, nullable: true }) subID: string diff --git a/backend/migrations/mysql/2026_03_10_subscribe_reminder.sql b/backend/migrations/mysql/2026_03_10_subscribe_reminder.sql new file mode 100644 index 000000000..8759b24af --- /dev/null +++ b/backend/migrations/mysql/2026_03_10_subscribe_reminder.sql @@ -0,0 +1 @@ +ALTER TABLE `user` ADD COLUMN `subscribeReminderSentOn` timestamp DEFAULT NULL;