Skip to content

Commit 14bb0e7

Browse files
talygurynCopilotCopilot
authored
Feature: reminder email for blocked workspace (#484)
* Add blocked workspace reminder email template Introduces a new email template for reminding users about blocked workspaces due to expired or depleted plans. Adds pluralization utility for Russian, updates template names, and includes logic to calculate days since the last payment. * Send reminder emails to blocked workspace admins Added logic to send reminder emails to admins of blocked workspaces at specific intervals after payday. Introduced the sendBlockedWorkspaceReminders method and updated tests to cover this new behavior. * Fix workspace retrieval and header in EmailTestServer Corrects the Content-Type header to include charset in sendHTML and fixes the getWorkspace method to properly return the workspace document. Also removes a redundant line in calculateDaysAfterPayday. * Add comments and eslint disables for magic numbers Added explanatory comments and disabled the @typescript-eslint/no-magic-numbers rule for specific lines in emailOverview.ts and paymaster/src/index.ts where magic numbers are used for date calculations and reminder scheduling. * Update index.test.ts * Update test to use expect.any(Number) for daysAfterPayday Replaces the hardcoded value for daysAfterPayday with expect.any(Number) in the blocked-workspace-reminder test payload to allow for dynamic number matching. * Move pluralize_ru macro from Twig to TypeScript extension Removed the utils.twig file and its pluralize_ru macro, replacing it with a TypeScript implementation registered as a Twig function in extensions.ts. Updated all template usages to call pluralize_ru directly, simplifying imports and improving maintainability. * Refactor blocked workspace reminder days to constant Introduced DAYS_AFTER_PAYDAY_TO_REMIND constant to define the days after payday when admins are reminded about blocked workspaces. Updated the logic to use this constant for better maintainability and clarity. * Fix test dates and assertions in PaymasterWorker tests Updated test dates and moved the blocked-workspace-reminder assertion to the correct test case in index.test.ts. This ensures the tests accurately reflect the intended scenarios and improve test reliability. * Add test for blocked workspace reminder after payday Introduces a test to verify that admins are reminded for blocked workspaces with active subscriptions when 3 days have passed since payday. Ensures the reminder includes the correct number of days after payday. * Rename daysBlocked to daysAfterPayday in reminder method Updated the sendBlockedWorkspaceReminders method to use 'daysAfterPayday' instead of 'daysBlocked' for clarity and consistency in parameter naming and payload structure. * Add eslint-disable for magic numbers in pluralize_ru Added eslint-disable-next-line comments to suppress magic number warnings in the pluralize_ru Twig function. This helps maintain code clarity while adhering to linting rules. * Update extensions.ts * Add blocked workspace reminder notification support Introduces handling for 'blocked-workspace-reminder' tasks in SenderWorker, including new types and template variables. Renames files for consistency and updates type exports to support the new notification event. * Update provider.ts * Add documentation to calculateDaysAfterPayday method Added JSDoc comments to the calculateDaysAfterPayday method in EmailTestServer to clarify its purpose and usage. This improves code readability and maintainability. * Simplify pluralization logic in blocked workspace emails Removed redundant ternary check for daysAfterPayday in pluralize_ru calls across HTML, subject, and text templates for blocked workspace reminder emails. This streamlines the template code and ensures consistent pluralization. * Update workers/email/scripts/emailOverview.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/paymaster/tests/index.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/paymaster/src/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/paymaster/src/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/email/src/templates/emails/blocked-workspace-reminder/subject.twig Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/email/src/templates/emails/blocked-workspace-reminder/text.twig Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/email/src/templates/emails/blocked-workspace-reminder/html.twig Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/sender/types/sender-task/blocked-workspace-reminder.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/sender/types/sender-task/blocked-workspace-reminder.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add lastChargeDate fallback to calculateDaysAfterPayday in email overview (#485) * Initial plan * Add fallback logic for lastChargeDate in calculateDaysAfterPayday Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Remove accidentally committed build artifacts Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Remove env-test.js build artifact Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Fix setMonth mutation issue in calculateDaysAfterPayday Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Clean up build artifacts Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Move MILLISECONDS_IN_DAY to module level Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Stop tracking build artifacts and update gitignore Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Fix grammar in MILLISECONDS_IN_DAY comment Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Expand test coverage for blocked workspace reminder days (#486) * Initial plan * Add comprehensive test coverage for blocked workspace reminder days Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com> * Update blocked workspace reminder email text Revised subject and body to emphasize required action and clarify that error monitoring is not working. The new wording provides clearer instructions for users to renew their subscription. * Group blocked workspace reminder tests Refactored blocked workspace reminder tests into a dedicated describe block for better organization and readability. * Refactor blocked workspace reminder tests Consolidated repetitive test logic for blocked workspace reminders into a reusable helper function. This improves maintainability and readability of the test suite by reducing code duplication. * Fix extra closing bracket in test suite Removed an unnecessary closing bracket in the PaymasterWorker test suite to correct the test structure. * Refactor payday calculation logic to shared utility Moved daysBeforePayday and daysAfterPayday functions to a new shared utility file (lib/utils/payday.ts) and updated emailOverview and paymaster worker to use these functions. This reduces code duplication and centralizes payday-related calculations. * Update workers/sender/src/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix spacing in blocked workspace email templates Added missing space after '{{' in variable references in subject and text templates for blocked workspace reminder emails to improve readability and consistency. * Update index.ts * Move updateLastNoticationDate call outside conditionals Refactored the code to call updateLastNoticationDate after processing notifications, regardless of whether the conditional block is entered. This ensures the last notification date is always updated for each workspace and event type. * Move updateLastNoticationDate call outside Promise.all Refactored the updateLastNoticationDate invocation to occur after Promise.all resolves, ensuring it is called once per method execution rather than for each iteration. * Fix typo in updateLastNotificationDate method name Renamed updateLastNoticationDate to updateLastNotificationDate for consistency and correctness across all usages in SenderWorker. * Update blocked-workspace-reminder.ts --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: talyguryn <15259299+talyguryn@users.noreply.github.com>
1 parent f977eba commit 14bb0e7

18 files changed

Lines changed: 437 additions & 72 deletions

File tree

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ coverage
66
.DS_Store
77
globalConfig.json
88
*.log
9+
*.js
10+
!jest.config.js
11+
!jest.global-teardown.js
12+
!jest.setup.js
13+
!jest.setup.mongo-repl-set.js
14+
!jest.setup.redis-mock.js
15+
!jest-mongodb-config.js
16+
!migrate-mongo-config.js
17+
!/env.js
18+
!convertors/**/*.js
19+
!tools/**/*.js
20+
!bin/**/*.js

lib/utils/payday.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { HOURS_IN_DAY, MINUTES_IN_HOUR, SECONDS_IN_MINUTE, MS_IN_SEC } from './consts';
2+
3+
/**
4+
* Milliseconds in day. Needs for calculating difference between dates in days.
5+
*/
6+
const MILLISECONDS_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE * MS_IN_SEC;
7+
8+
/**
9+
* Returns difference between now and payday in days
10+
*
11+
* Pay day is calculated by formula: paidUntil date or last charge date + 1 month
12+
*
13+
* @param date - last charge date
14+
* @param paidUntil - paid until date
15+
* @param isDebug - flag for debug purposes
16+
*/
17+
export function daysBeforePayday(date: Date, paidUntil: Date = null, isDebug = false): number {
18+
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);
19+
20+
if (isDebug) {
21+
expectedPayDay.setDate(date.getDate() + 1);
22+
} else if (!paidUntil) {
23+
expectedPayDay.setMonth(date.getMonth() + 1);
24+
}
25+
26+
const now = new Date().getTime();
27+
28+
return Math.floor((expectedPayDay.getTime() - now) / MILLISECONDS_IN_DAY);
29+
}
30+
31+
/**
32+
* Returns difference between payday and now in days
33+
*
34+
* Pay day is calculated by formula: paidUntil date or last charge date + 1 month
35+
*
36+
* @param date - last charge date
37+
* @param paidUntil - paid until date
38+
* @param isDebug - flag for debug purposes
39+
*/
40+
export function daysAfterPayday(date: Date, paidUntil: Date = null, isDebug = false): number {
41+
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);
42+
43+
if (isDebug) {
44+
expectedPayDay.setDate(date.getDate() + 1);
45+
} else if (!paidUntil) {
46+
expectedPayDay.setMonth(date.getMonth() + 1);
47+
}
48+
49+
const now = new Date().getTime();
50+
51+
return Math.floor((now - expectedPayDay.getTime()) / MILLISECONDS_IN_DAY);
52+
}

workers/email/scripts/emailOverview.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ObjectId } from 'mongodb';
1919
import * as path from 'path';
2020
import * as dotenv from 'dotenv';
2121
import { HttpStatusCode } from '../../../lib/utils/consts';
22+
import { daysAfterPayday } from '../../../lib/utils/payday';
2223

2324
/**
2425
* Merge email worker .env and root workers .env
@@ -147,6 +148,7 @@ class EmailTestServer {
147148
user,
148149
period: 10,
149150
reason: 'error on the payment server side',
151+
daysAfterPayday: await this.calculateDaysAfterPayday(workspace),
150152
};
151153

152154
try {
@@ -210,7 +212,7 @@ class EmailTestServer {
210212
*/
211213
private sendHTML(html: string, response: http.ServerResponse): void {
212214
response.writeHead(HttpStatusCode.Ok, {
213-
'Content-Type': 'text/html',
215+
'Content-Type': 'text/html; charset=utf-8',
214216
});
215217
response.write(html);
216218
response.end();
@@ -323,6 +325,25 @@ class EmailTestServer {
323325
return connection.collection('workspaces').findOne({ _id: new ObjectId(workspaceId) });
324326
}
325327

328+
/**
329+
* Calculate days after payday
330+
* Return number of days after payday. If payday is in the future, return 0
331+
*
332+
* @param workspace - workspace data
333+
* @returns {Promise<number>} number of days after payday
334+
*/
335+
private async calculateDaysAfterPayday(
336+
workspace: WorkspaceDBScheme
337+
): Promise<number> {
338+
if (!workspace.lastChargeDate) {
339+
return 0;
340+
}
341+
342+
const days = daysAfterPayday(workspace.lastChargeDate, workspace.paidUntil);
343+
344+
return days > 0 ? days : 0;
345+
}
346+
326347
/**
327348
* Get user info
328349
*

workers/email/src/provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default class EmailProvider extends NotificationsProvider {
4444
switch (notification.type) {
4545
case 'assignee': templateName = Templates.Assignee; break;
4646
case 'block-workspace': templateName = Templates.BlockWorkspace; break;
47+
case 'blocked-workspace-reminder': templateName = Templates.BlockedWorkspaceReminder; break;
4748
case 'days-limit-almost-reached': templateName = Templates.DaysLimitAlmostReached; break;
4849
case 'event': templateName = Templates.Event; break;
4950
case 'events-limit-almost-reached': templateName = Templates.EventsLimitAlmostReached; break;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{% extends '../../components/layout.twig' %}
2+
3+
{% block header %}
4+
{% include '../../components/workspace.twig' with {workspace: workspace} %}
5+
{% endblock %}
6+
7+
{% block content %}
8+
<tr>
9+
<td>
10+
<img src="{{ hostOfStatic }}/email/low-balance-icon.png" width="32" height="32" hspace="3" style="display: block; vertical-align: middle; margin: 23px auto 0;">
11+
</td>
12+
</tr>
13+
<tr>
14+
<td align="center" style="padding: 15px 0;">
15+
<font color="#dbe6ff" style="font-size: 15px; text-align: center; color: #dbe6ff; letter-spacing: 0.4px;">
16+
<span style="vertical-align: middle; display: inline-block;">
17+
{{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }} без мониторинга
18+
</span>
19+
</font>
20+
</td>
21+
</tr>
22+
<tr>
23+
<td style="display: block; padding: 20px; margin-bottom: 30px; border-width: 1px; border-color: #494f5e; border-style: solid; border-radius: 10px; line-height: 1.47">
24+
<font color="#dbe6ff" style="font-size: 15px; letter-spacing: 0.4px;">
25+
<p style="margin-top: 0;">
26+
Напоминаем, что мониторинг ошибок в «{{ workspace.name | escape }}» всё ещё остановлен, потому что закончился лимит или срок действия тарифного плана.
27+
</p>
28+
<p style="margin-bottom: 0;">
29+
Чтобы снова видеть актуальные события, выберите подходящий тарифный план и продлите подписку в настройках оплаты.
30+
</p>
31+
</font>
32+
</td>
33+
</tr>
34+
<tr>
35+
<td style="padding-right: 20px; padding-left: 20px; padding-bottom: 40px;">
36+
{% include '../../components/button.twig' with {href: host ~ '/workspace/' ~ workspace._id ~ '/settings/billing', label: workspace.tariffPlanId is same as('5f47f031ff71510040f433c1') ? 'Выбрать тариф от 99 ₽' : 'Открыть настройки'} %}
37+
</td>
38+
</tr>
39+
{% endblock %}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }}
2+
3+
Чтобы снова видеть актуальные события, выберите подходящий тарифный план и продлите подписку в настройках оплаты: {{ host }}/workspace/{{ workspace._id }}/settings/billing
4+
5+
***
6+
7+
Хоук
8+
Российский трекер ошибок
9+
10+
Made by CodeX

workers/email/src/templates/extensions.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,29 @@ Twig.extendFilter('abbrNumber', (value: number): string => {
154154
Twig.extendFilter('sortEvents', (events: TemplateEventData[]): TemplateEventData[] => {
155155
return events.sort((a, b) => a.newCount - b.newCount);
156156
});
157+
158+
/**
159+
* Pluralize Russian words based on a number
160+
*
161+
* @param {number} n - the number to determine the form
162+
* @param {string[]} forms - array of word forms [singular, few, many]
163+
* @returns {string}
164+
*/
165+
Twig.extendFunction('pluralize_ru', (n: number, forms: string[]): string => {
166+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
167+
if (n % 100 >= 11 && n % 100 <= 19) {
168+
return forms[2];
169+
}
170+
171+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
172+
const last = n % 10;
173+
174+
if (last === 1) {
175+
return forms[0];
176+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
177+
} else if (last >= 2 && last <= 4) {
178+
return forms[1];
179+
} else {
180+
return forms[2];
181+
}
182+
});

workers/email/src/templates/names.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
enum Templates {
55
Assignee = 'assignee',
66
BlockWorkspace = 'block-workspace',
7+
BlockedWorkspaceReminder = 'blocked-workspace-reminder',
78
DaysLimitAlmostReached = 'days-limit-almost-reached',
89
Event = 'event',
910
EventsLimitAlmostReached = 'events-limit-almost-reached',

workers/paymaster/src/index.ts

Lines changed: 37 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,14 @@ import { Collection } from 'mongodb';
77
import { PlanDBScheme, WorkspaceDBScheme } from '@hawk.so/types';
88
import { EventType, PaymasterEvent } from '../types/paymaster-worker-events';
99
import axios from 'axios';
10-
import { HOURS_IN_DAY, MINUTES_IN_HOUR, MS_IN_SEC, SECONDS_IN_MINUTE } from '../../../lib/utils/consts';
1110
import * as WorkerNames from '../../../lib/workerNames';
1211
import HawkCatcher from '@hawk.so/nodejs';
12+
import { daysBeforePayday, daysAfterPayday } from '../../../lib/utils/payday';
1313

1414
dotenv.config({
1515
path: path.resolve(__dirname, '../.env'),
1616
});
1717

18-
/**
19-
* Milliseconds in day. Needs for calculating difference between dates in days.
20-
*/
21-
const MILLISECONDS_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE * MS_IN_SEC;
22-
2318
/**
2419
* Days after payday to try paying in actual subscription
2520
* When days after payday is more than this const and we still
@@ -33,6 +28,12 @@ const DAYS_AFTER_PAYDAY_TO_TRY_PAYING = 3;
3328
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
3429
const DAYS_LEFT_ALERT = [3, 2, 1, 0];
3530

31+
/**
32+
* Days after payday to remind admins about blocked workspace
33+
*/
34+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
35+
const DAYS_AFTER_PAYDAY_TO_REMIND = [1, 2, 3, 5, 7, 30];
36+
3637
/**
3738
* Worker to check workspaces subscription status and ban workspaces without actual subscription
3839
*/
@@ -103,52 +104,6 @@ export default class PaymasterWorker extends Worker {
103104
return endDate;
104105
}
105106

106-
/**
107-
* Returns difference between now and payday in days
108-
*
109-
* Pay day is calculated by formula: paidUntil date or last charge date + 1 month
110-
*
111-
* @param date - last charge date
112-
* @param paidUntil - paid until date
113-
* @param isDebug - flag for debug purposes
114-
*/
115-
private static daysBeforePayday(date: Date, paidUntil: Date = null, isDebug = false): number {
116-
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);
117-
118-
if (isDebug) {
119-
expectedPayDay.setDate(date.getDate() + 1);
120-
} else if (!paidUntil) {
121-
expectedPayDay.setMonth(date.getMonth() + 1);
122-
}
123-
124-
const now = new Date().getTime();
125-
126-
return Math.floor((expectedPayDay.getTime() - now) / MILLISECONDS_IN_DAY);
127-
}
128-
129-
/**
130-
* Returns difference between payday and now in days
131-
*
132-
* Pay day is calculated by formula: paidUntil date or last charge date + 1 month
133-
*
134-
* @param date - last charge date
135-
* @param paidUntil - paid until date
136-
* @param isDebug - flag for debug purposes
137-
*/
138-
private static daysAfterPayday(date: Date, paidUntil: Date = null, isDebug = false): number {
139-
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);
140-
141-
if (isDebug) {
142-
expectedPayDay.setDate(date.getDate() + 1);
143-
} else if (!paidUntil) {
144-
expectedPayDay.setMonth(date.getMonth() + 1);
145-
}
146-
147-
const now = new Date().getTime();
148-
149-
return Math.floor((now - expectedPayDay.getTime()) / MILLISECONDS_IN_DAY);
150-
}
151-
152107
/**
153108
* Start consuming messages
154109
*/
@@ -247,13 +202,13 @@ export default class PaymasterWorker extends Worker {
247202
* How many days have passed since payments the expected day of payments
248203
*/
249204
// @ts-expect-error debug
250-
const daysAfterPayday = PaymasterWorker.daysAfterPayday(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug);
205+
const daysAfterPaydayValue = daysAfterPayday(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug);
251206

252207
/**
253208
* How many days left for the expected day of payments
254209
*/
255210
// @ts-expect-error debug
256-
const daysLeft = PaymasterWorker.daysBeforePayday(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug);
211+
const daysLeft = daysBeforePayday(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug);
257212

258213
/**
259214
* Do we need to ask for money
@@ -319,9 +274,15 @@ export default class PaymasterWorker extends Worker {
319274

320275
/**
321276
* Time to pay but workspace has paid plan
322-
* If it is blocked then do nothing
277+
* If it is blocked then remind admins about it
323278
*/
324279
if (workspace.isBlocked) {
280+
// Send reminders on certain days after payday
281+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
282+
if (DAYS_AFTER_PAYDAY_TO_REMIND.includes(daysAfterPaydayValue)) {
283+
await this.sendBlockedWorkspaceReminders(workspace, daysAfterPaydayValue);
284+
}
285+
325286
return [workspace, true];
326287
}
327288

@@ -338,7 +299,7 @@ export default class PaymasterWorker extends Worker {
338299
* Block workspace if it has paid subscription,
339300
* but a few days have passed after payday
340301
*/
341-
if (daysAfterPayday > DAYS_AFTER_PAYDAY_TO_TRY_PAYING) {
302+
if (daysAfterPaydayValue > DAYS_AFTER_PAYDAY_TO_TRY_PAYING) {
342303
await this.blockWorkspace(workspace);
343304

344305
return [workspace, true];
@@ -403,6 +364,26 @@ export default class PaymasterWorker extends Worker {
403364
});
404365
}
405366

367+
368+
/**
369+
* Sends reminder emails to blocked workspace admins
370+
*
371+
* @param workspace - workspace to send reminders for
372+
* @param days - number of days the workspace spent after payday
373+
*/
374+
private async sendBlockedWorkspaceReminders(
375+
workspace: WorkspaceDBScheme,
376+
days: number
377+
): Promise<void> {
378+
await this.addTask(WorkerNames.EMAIL, {
379+
type: 'blocked-workspace-reminder',
380+
payload: {
381+
workspaceId: workspace._id.toString(),
382+
daysAfterPayday: days,
383+
},
384+
});
385+
}
386+
406387
/**
407388
* Sets BillingPeriodEventsCount to 0 in workspace
408389
*

0 commit comments

Comments
 (0)