placementt-core 1.400.1237 → 1.400.1239

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/lib/renewals.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.renewalSituation = exports.hasRenewal = exports.RENEWAL_TEMPLATE_IDS = exports.RENEWAL_FINANCE_STEPS = exports.RENEWAL_STEP_LABELS = exports.RENEWAL_STEPS = void 0;
3
+ exports.renewalTaskSchedule = exports.renewalSituation = exports.hasRenewal = exports.RENEWAL_EMAIL_TEMPLATES = exports.RENEWAL_FINANCE_STEPS = exports.RENEWAL_STEP_LABELS = exports.RENEWAL_STEPS = void 0;
4
4
  const outreachRules_1 = require("./outreachRules");
5
5
  exports.RENEWAL_STEPS = ["headsUp", "yearInReview", "sixtyDay", "thirtyDay", "thanks"];
6
6
  exports.RENEWAL_STEP_LABELS = {
@@ -12,14 +12,13 @@ exports.RENEWAL_STEP_LABELS = {
12
12
  };
13
13
  /** Steps that copy in the finance contact as well as the school's contacts. */
14
14
  exports.RENEWAL_FINANCE_STEPS = ["headsUp", "sixtyDay", "thirtyDay"];
15
- /** Brevo-style ids for the renewal emails, written in the backend
16
- * (awsSES/templates/engagement.ts) like the onboarding ones. */
17
- exports.RENEWAL_TEMPLATE_IDS = {
18
- headsUp: 9101,
19
- yearInReview: 9102,
20
- sixtyDay: 9103,
21
- thirtyDay: 9104,
22
- thanks: 9105,
15
+ /** The admin email template (constants.ts emailTemplates) for each step. */
16
+ exports.RENEWAL_EMAIL_TEMPLATES = {
17
+ headsUp: "engagementRenewalPrice",
18
+ yearInReview: "engagementRenewalReview",
19
+ sixtyDay: "engagementRenewalDue",
20
+ thirtyDay: "engagementRenewalInvoice",
21
+ thanks: "engagementRenewalThanks",
23
22
  };
24
23
  const DAY_MS = 86400000;
25
24
  const ymdToMs = (ymd) => Date.parse(`${ymd}T00:00:00Z`);
@@ -87,4 +86,38 @@ const renewalSituation = ({ institute, products, now }) => {
87
86
  return { ...base, lapsed: false, stage: "early" };
88
87
  };
89
88
  exports.renewalSituation = renewalSituation;
89
+ const addDays = (ymd, days) => new Date(ymdToMs(ymd) + days * DAY_MS).toISOString().slice(0, 10);
90
+ /**
91
+ * The renewal tasks for one renewal date, each with the day it is due: the
92
+ * same timings as renewalSituation, worked out once when the date is set
93
+ * rather than checked daily. The heads-up is the last 20 April and the year
94
+ * in review the last 1 July that fall within the year before the 60-day
95
+ * email. A date set late skips the steps already gone, but keeps the most
96
+ * recent one, since that's the step the customer is in. The thanks is not
97
+ * here: it is due when the date moves on.
98
+ *
99
+ * @param {string} renewalDate YYYY-MM-DD.
100
+ * @param {string} today YYYY-MM-DD, London.
101
+ * @return {object[]} each step and its due date, plus "lapsed" the day after renewal.
102
+ */
103
+ const renewalTaskSchedule = (renewalDate, today) => {
104
+ const sixty = addDays(renewalDate, -60);
105
+ const yearBefore = addDays(renewalDate, -365);
106
+ const lastBefore = (md) => {
107
+ const year = Number(sixty.slice(0, 4));
108
+ const candidate = `${year}-${md}` < sixty ? `${year}-${md}` : `${year - 1}-${md}`;
109
+ return candidate >= yearBefore ? candidate : undefined;
110
+ };
111
+ const all = [
112
+ { step: "headsUp", due: lastBefore("04-20") },
113
+ { step: "yearInReview", due: lastBefore("07-01") },
114
+ { step: "sixtyDay", due: sixty },
115
+ { step: "thirtyDay", due: addDays(renewalDate, -30) },
116
+ ]
117
+ .filter((t) => Boolean(t.due))
118
+ .sort((a, b) => a.due.localeCompare(b.due));
119
+ const gone = all.filter((t) => t.due < today);
120
+ return [...all.filter((t) => t.due >= today || t === gone[gone.length - 1]), { step: "lapsed", due: addDays(renewalDate, 1) }];
121
+ };
122
+ exports.renewalTaskSchedule = renewalTaskSchedule;
90
123
  //# sourceMappingURL=renewals.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"renewals.js","sourceRoot":"","sources":["../src/renewals.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"renewals.js","sourceRoot":"","sources":["../src/renewals.ts"],"names":[],"mappings":"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"}
@@ -50,4 +50,21 @@ describe("renewals", () => {
50
50
  expect(at(thanked, "2026-11-10").due).toBeUndefined();
51
51
  });
52
52
  });
53
+ describe("renewalTaskSchedule", () => {
54
+ it("dates each renewal task from the renewal date", () => {
55
+ expect((0, renewals_1.renewalTaskSchedule)("2027-10-01", "2026-11-03")).toEqual([
56
+ { step: "headsUp", due: "2027-04-20" },
57
+ { step: "yearInReview", due: "2027-07-01" },
58
+ { step: "sixtyDay", due: "2027-08-02" },
59
+ { step: "thirtyDay", due: "2027-09-01" },
60
+ { step: "lapsed", due: "2027-10-02" },
61
+ ]);
62
+ });
63
+ it("keeps only the step a late date is in, of the ones already gone", () => {
64
+ expect((0, renewals_1.renewalTaskSchedule)("2026-12-15", "2026-11-03").map((t) => t.step)).toEqual(["sixtyDay", "thirtyDay", "lapsed"]);
65
+ });
66
+ it("leaves out a heads-up or year in review more than a year before the 60-day email", () => {
67
+ expect((0, renewals_1.renewalTaskSchedule)("2027-06-01", "2026-11-03").map((t) => t.step)).toEqual(["yearInReview", "sixtyDay", "thirtyDay", "lapsed"]);
68
+ });
69
+ });
53
70
  //# sourceMappingURL=renewals.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"renewals.test.js","sourceRoot":"","sources":["../src/renewals.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"renewals.test.js","sourceRoot":"","sources":["../src/renewals.test.ts"],"names":[],"mappings":"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"}
@@ -2026,7 +2026,7 @@ export type EmailTemplateConfig = {
2026
2026
  text: string;
2027
2027
  link: string;
2028
2028
  }[];
2029
- type: "workflow" | "feedback" | "event" | "campaign" | "database" | "onboarding" | "skillsInvite" | "aspirationInvite" | "leaverInvite" | "leaverResultsInvite" | "leaverChristmasInvite";
2029
+ type: "workflow" | "feedback" | "event" | "campaign" | "database" | "onboarding" | "skillsInvite" | "aspirationInvite" | "leaverInvite" | "leaverResultsInvite" | "leaverChristmasInvite" | "engagement";
2030
2030
  product: EmailTemplateProduct;
2031
2031
  sections?: EmailSectionNames[];
2032
2032
  group?: string;
@@ -2038,7 +2038,7 @@ export type EmailTemplateConfig = {
2038
2038
  locked?: boolean;
2039
2039
  };
2040
2040
  export type ProductTag = ProductKey | "employerEvents" | "hub";
2041
- export type EmailTemplateProduct = ProductTag;
2041
+ export type EmailTemplateProduct = ProductTag | "admin";
2042
2042
  export type EmailCampaign = {
2043
2043
  id: string;
2044
2044
  oId: string;
@@ -3870,7 +3870,8 @@ export type YearScoreData = {
3870
3870
  campaignsClicked?: number;
3871
3871
  campaignsFailed?: number;
3872
3872
  };
3873
- type MessageTypes = "hubOrTrustInviteSchool" | "sendPlacementActionRequired" | "sendPlacementAdditionalInformationRequired" | "sendFeedbackReminder" | "sendProviderContactInvite" | "sendProviderContactActivityInvite" | "sendActivateUserAccount" | "shareEmployerEventRequest" | "employerEventInvite" | "schoolEventInvite" | "nonUserSchoolEventInvite" | "reuploadPlacementDocument" | "requestEventDates" | "taskReminder" | "eventDatesReminder" | "parentDatabase" | "aspirationInvite" | "leaverInvite" | "leaverReminder" | "leaverResultsInvite" | "leaverResultsReminder" | "leaverChristmasInvite" | "leaverChristmasReminder" | "scheduledCheckUpcoming" | "scheduledCheckDue" | "scheduledCheckLate" | "alumniCheckInInvite" | // annual "where are you now" check-in — reminder chain falls back to the trusted contact
3873
+ type MessageTypes = "hubOrTrustInviteSchool" | "engagementEmail" | // an admin email template sent to a school (featureEngagement.ts)
3874
+ "sendPlacementActionRequired" | "sendPlacementAdditionalInformationRequired" | "sendFeedbackReminder" | "sendProviderContactInvite" | "sendProviderContactActivityInvite" | "sendActivateUserAccount" | "shareEmployerEventRequest" | "employerEventInvite" | "schoolEventInvite" | "nonUserSchoolEventInvite" | "reuploadPlacementDocument" | "requestEventDates" | "taskReminder" | "eventDatesReminder" | "parentDatabase" | "aspirationInvite" | "leaverInvite" | "leaverReminder" | "leaverResultsInvite" | "leaverResultsReminder" | "leaverChristmasInvite" | "leaverChristmasReminder" | "scheduledCheckUpcoming" | "scheduledCheckDue" | "scheduledCheckLate" | "alumniCheckInInvite" | // annual "where are you now" check-in — reminder chain falls back to the trusted contact
3874
3875
  "alumniCheckInTrustedContact" | // parent/trusted-contact fallback invite — has its own reminder chain
3875
3876
  SurveyMessageType;
3876
3877
  /** Every message type the survey engine sends, derived from the registry so the two
@@ -3882,7 +3883,8 @@ export type SurveyMessageType = `survey${Capitalize<SurveyKind>}${"Invite" | "Re
3882
3883
  * is exhausted with no response. Params are read from the interaction's additionalData. */
3883
3884
  export type ReminderFallbackType = "checkInTrustedContact" | // legacy name for the below; live interaction docs still carry it
3884
3885
  "surveyParentFallback" | // chase the parent/trusted contact when the subject ignores their whole chain
3885
- "alumniCheckInFallback";
3886
+ "alumniCheckInFallback" | // the same, for the annual alumni check-in, which is not a survey
3887
+ "engagementStuck";
3886
3888
  export type EmailInteraction = {
3887
3889
  messageId: string;
3888
3890
  collectionLink: string;
@@ -4473,16 +4475,7 @@ export type CustomerSuccessData = {
4473
4475
  metrics?: CustomerSuccessMetrics;
4474
4476
  };
4475
4477
  export type InstituteEngagement = {
4476
- /** Last engagement email of any kind, for the one-per-week cap. */
4477
- lastEmailAt?: string;
4478
4478
  onboarding?: {
4479
- /** The step last nudged, so the same step is not sent twice in a row
4480
- * without a change (the cron caps repeats of one step). */
4481
- lastStepKey?: string;
4482
- lastVariant?: "setup" | "resend";
4483
- lastSentAt?: string;
4484
- /** How many times in a row the same step has been sent. */
4485
- repeats?: number;
4486
4479
  /** Optional steps whose emails ran out unanswered, keyed by step, with
4487
4480
  * the date. They count as done so the next step can start. */
4488
4481
  passed?: {
@@ -4548,21 +4541,6 @@ export type FeatureCampaign = {
4548
4541
  adoptedAt?: string;
4549
4542
  endedAt?: string;
4550
4543
  };
4551
- /** `engagementEmails/{autoId}`: one flat log document per send, for the
4552
- * admin view. */
4553
- export type EngagementEmailLog = {
4554
- oId: string;
4555
- kind: "marketing" | "onboarding" | "renewal";
4556
- key: string;
4557
- variant?: "setup" | "resend";
4558
- touch?: number;
4559
- templateId: number;
4560
- recipients: string[];
4561
- sentAt: string;
4562
- campaignId?: string;
4563
- /** The approved draft it was sent from. */
4564
- draftId?: string;
4565
- };
4566
4544
  export type StudentRoute = {
4567
4545
  route: "wonde" | "mis" | "csv";
4568
4546
  chosenAt: string;
@@ -4592,48 +4570,6 @@ export type EngagementPlan = {
4592
4570
  savedAt: string;
4593
4571
  savedBy: string;
4594
4572
  };
4595
- /** Where an engagement email is in its approval: written as a draft, approved
4596
- * by an admin for the next school morning, then sent; or skipped by an admin,
4597
- * expired unapproved, or dropped because the school did the step first. */
4598
- export type EngagementDraftStatus = "draft" | "approved" | "sent" | "skipped" | "expired" | "dropped";
4599
- /** `engagementDrafts/{autoId}`: one engagement email to one institute. The
4600
- * daily check writes every email as one of these and nothing sends until an
4601
- * admin approves it. */
4602
- export type EngagementDraft = {
4603
- oId: string;
4604
- instituteName: string;
4605
- kind: "marketing" | "onboarding" | "renewal";
4606
- key: string;
4607
- variant?: "setup" | "resend";
4608
- touch?: number;
4609
- templateId: number;
4610
- label: string;
4611
- description: string;
4612
- path: string;
4613
- campaignId?: string;
4614
- /** Onboarding: `engagement.onboarding.repeats` once this is sent. */
4615
- repeats?: number;
4616
- recipients: {
4617
- email: string;
4618
- forename: string;
4619
- surname: string;
4620
- }[];
4621
- /** Drafts with the same template and step, approved together. */
4622
- batchKey: string;
4623
- status: EngagementDraftStatus;
4624
- createdAt: string;
4625
- approvedAt?: string;
4626
- approvedBy?: string;
4627
- /** 7:30am London on the school day after approval. */
4628
- sendAt?: string;
4629
- sentAt?: string;
4630
- sentTo?: string[];
4631
- /** When it was skipped, expired or dropped, and why. */
4632
- closedAt?: string;
4633
- closedReason?: string;
4634
- /** An admin changed the description or recipients. */
4635
- edited?: boolean;
4636
- };
4637
4573
  export type CustomerTask = {
4638
4574
  id: string;
4639
4575
  customerId: string;
@@ -4651,6 +4587,8 @@ export type CustomerTask = {
4651
4587
  completedBy?: string;
4652
4588
  triggerType: "manual" | "renewalLogged" | "timeBased" | "metricThreshold" | "calendar";
4653
4589
  triggerKey?: string;
4590
+ /** The admin email template (constants.ts emailTemplates) this task sends, from the task. */
4591
+ emailTemplate?: string;
4654
4592
  commId?: string;
4655
4593
  commType?: "users";
4656
4594
  metadata?: {
@@ -1 +1 @@
1
- {"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AA4zIA,sDASC;AAeD,oDAKC;AAaD,0CAEC;AAvtED;;;;;kFAKkF;AACrE,QAAA,qBAAqB,GAAG,SAAS,CAAC;AAClC,QAAA,iBAAiB,GAAG,OAAO,CAAC;AAmoEzC;yEACyE;AAC5D,QAAA,0BAA0B,GAA2C;IAC9E,gBAAgB,EAAE,2BAA2B;IAC7C,mBAAmB,EAAE,sBAAsB;IAC3C,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,oCAAoC;IAChD,aAAa,EAAE,yBAAyB;IACxC,aAAa,EAAE,wBAAwB;IACvC,KAAK,EAAE,OAAO;CACjB,CAAC;AAEW,QAAA,0BAA0B,GAA+C;IAClF,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,SAAgB,qBAAqB,CAAC,SAAsC;IACxE,QAAQ,SAAS,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC;QAC5B,KAAK,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC;QAC7B,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,OAAO,CAAC;QACb,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;IAC3B,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,oBAAoB,CAChC,IAAwC,EACxC,MAA6C;IAE7C,OAAO,IAAI,EAAE,WAAW,IAAI,MAAM,EAAE,YAAY,IAAI,KAAK,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,KAA6D;IACzF,OAAO,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;AAC/E,CAAC"}
1
+ {"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AA+zIA,sDASC;AAeD,oDAKC;AAaD,0CAEC;AAztED;;;;;kFAKkF;AACrE,QAAA,qBAAqB,GAAG,SAAS,CAAC;AAClC,QAAA,iBAAiB,GAAG,OAAO,CAAC;AAqoEzC;yEACyE;AAC5D,QAAA,0BAA0B,GAA2C;IAC9E,gBAAgB,EAAE,2BAA2B;IAC7C,mBAAmB,EAAE,sBAAsB;IAC3C,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,oCAAoC;IAChD,aAAa,EAAE,yBAAyB;IACxC,aAAa,EAAE,wBAAwB;IACvC,KAAK,EAAE,OAAO;CACjB,CAAC;AAEW,QAAA,0BAA0B,GAA+C;IAClF,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,SAAgB,qBAAqB,CAAC,SAAsC;IACxE,QAAQ,SAAS,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC;QAC5B,KAAK,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC;QAC7B,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,OAAO,CAAC;QACb,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;IAC3B,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,oBAAoB,CAChC,IAAwC,EACxC,MAA6C;IAE7C,OAAO,IAAI,EAAE,WAAW,IAAI,MAAM,EAAE,YAAY,IAAI,KAAK,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,KAA6D;IACzF,OAAO,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;AAC/E,CAAC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.400.1237",
5
+ "version": "1.400.1239",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
package/src/constants.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {AnalyticsItem, ApplicantStage, OutreachDeliverability, OutreachStatus, CommSchema, CustomFormSchema, CustomFormSchemaField, EmailTemplateConfig, FlagCodes, ProductTag, LeadStageSchema, StudentActivity, StudentActivityCategory, StudentActivityType, SurveyDateRule, SurveyOrdinal, SurveyWeekday, WorkflowStage} from "./typeDefinitions";
1
+ import {AnalyticsItem, ApplicantStage, OutreachDeliverability, OutreachStatus, CommSchema, CustomFormSchema, CustomFormSchemaField, EmailTemplateConfig, EmailTemplateProduct, FlagCodes, ProductTag, LeadStageSchema, StudentActivity, StudentActivityCategory, StudentActivityType, SurveyDateRule, SurveyOrdinal, SurveyWeekday, WorkflowStage} from "./typeDefinitions";
2
2
 
3
3
  import type {ResolvedProducts} from "./productResolver";
4
4
 
@@ -1237,6 +1237,8 @@ const placementRejectedParams = [...placementEmailParams, "reason"];
1237
1237
  // Emails to the cohort contact about the cohort as a whole.
1238
1238
  const staffCohortEmailParams = ["cohortName", "submissionDeadline", "daysUntilDeadline", "instituteName", "cohortContactForename", "cohortContactSurname", "cohortContactEmail"];
1239
1239
  // Cohort emails sent before a student has a placement.
1240
+ const engagementEmailParams = ["contactForename", "instituteName"];
1241
+
1240
1242
  const cohortEmailParams = ["studentForename", "studentSurname", "studentEmail", "parentEmail", "submissionDeadline", "daysUntilDeadline", "cohortName", "instituteName", "cohortContactForename", "cohortContactSurname", "cohortContactEmail"];
1241
1243
 
1242
1244
  export const emailTemplates:{[key: string]: EmailTemplateConfig} = {
@@ -1705,6 +1707,250 @@ export const emailTemplates:{[key: string]: EmailTemplateConfig} = {
1705
1707
  type: "leaverChristmasInvite",
1706
1708
  button: {text: "Quick check-in", link: "/leaverCheckpoint/{{requestId}}/christmas/{{token}}"},
1707
1709
  },
1710
+ // CareerThread's own onboarding and renewal emails to schools, sent by an admin from the institute's
1711
+ // emails. product "admin" keeps them out of every school's template list.
1712
+ engagementPickFeatures: {
1713
+ product: "admin",
1714
+ description: "First onboarding email: asks the school which parts of CareerThread they will use.",
1715
+ params: [...engagementEmailParams],
1716
+ button: {text: "Pick your features", link: "/institutes/home"},
1717
+ type: "engagement",
1718
+ name: "Pick your features",
1719
+ recipient: "staff",
1720
+ group: "Joining",
1721
+ },
1722
+ engagementPlanYear: {
1723
+ product: "admin",
1724
+ description: "Asks the school to drag each feature into the term they want to start it.",
1725
+ params: [...engagementEmailParams],
1726
+ button: {text: "Plan your year", link: "/institutes/organisation/overview"},
1727
+ type: "engagement",
1728
+ name: "Plan your year",
1729
+ recipient: "staff",
1730
+ group: "Joining",
1731
+ },
1732
+ engagementInviteStaff: {
1733
+ product: "admin",
1734
+ description: "Suggests inviting the rest of the careers team. Sent once, no reminders.",
1735
+ params: [...engagementEmailParams],
1736
+ button: {text: "Invite staff", link: "/institutes/organisation/staff"},
1737
+ type: "engagement",
1738
+ name: "Invite staff",
1739
+ recipient: "staff",
1740
+ group: "Joining",
1741
+ },
1742
+ engagementStudentRoute: {
1743
+ product: "admin",
1744
+ description: "Asks how the school will add students: Wonde, an MIS file or a spreadsheet.",
1745
+ params: [...engagementEmailParams, "routeCode"],
1746
+ optionalButtons: [
1747
+ {text: "Connect through Wonde", link: "/students-route/{{routeCode}}?route=wonde"},
1748
+ {text: "Send a file from our MIS", link: "/students-route/{{routeCode}}?route=mis"},
1749
+ {text: "Upload a spreadsheet", link: "/students-route/{{routeCode}}?route=csv"},
1750
+ ],
1751
+ type: "engagement",
1752
+ name: "Choose how to add students",
1753
+ recipient: "staff",
1754
+ group: "Students",
1755
+ },
1756
+ engagementWonde: {
1757
+ product: "admin",
1758
+ description: "Sent to the school and its IT contact once they choose Wonde.",
1759
+ params: [...engagementEmailParams],
1760
+ button: {text: "Data protection policy", link: "/data-protection-policy-july-2026.pdf"},
1761
+ type: "engagement",
1762
+ name: "Approve Wonde",
1763
+ recipient: "staff",
1764
+ group: "Students",
1765
+ },
1766
+ engagementMis: {
1767
+ product: "admin",
1768
+ description: "Sent to the school and its IT contact once they choose an MIS file transfer.",
1769
+ params: [...engagementEmailParams],
1770
+ button: {text: "Data protection policy", link: "/data-protection-policy-july-2026.pdf"},
1771
+ type: "engagement",
1772
+ name: "MIS file transfer",
1773
+ recipient: "staff",
1774
+ group: "Students",
1775
+ },
1776
+ engagementUploadStudents: {
1777
+ product: "admin",
1778
+ description: "Sent once the school chooses to upload a spreadsheet of students.",
1779
+ params: [...engagementEmailParams],
1780
+ button: {text: "Upload students", link: "/institutes/students"},
1781
+ type: "engagement",
1782
+ name: "Upload your students",
1783
+ recipient: "staff",
1784
+ group: "Students",
1785
+ },
1786
+ engagementWexGroups: {
1787
+ product: "admin",
1788
+ description: "Asks which year groups go on work experience, to set up a group for each.",
1789
+ params: [...engagementEmailParams],
1790
+ button: {text: "Set up a group", link: "/institutes/cohorts"},
1791
+ secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
1792
+ type: "engagement",
1793
+ name: "Set up work experience groups",
1794
+ recipient: "staff",
1795
+ group: "Work experience",
1796
+ },
1797
+ engagementWexCohort: {
1798
+ product: "admin",
1799
+ description: "Each new year: asks the school to check this year's cohort dates and students.",
1800
+ params: [...engagementEmailParams],
1801
+ button: {text: "Check this year's cohort", link: "/institutes/cohorts"},
1802
+ secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
1803
+ type: "engagement",
1804
+ name: "This year's cohort",
1805
+ recipient: "staff",
1806
+ group: "Work experience",
1807
+ },
1808
+ engagementWexWorkflow: {
1809
+ product: "admin",
1810
+ description: "Asks who signs off each placement, to build the workflow.",
1811
+ params: [...engagementEmailParams],
1812
+ button: {text: "Build the workflow", link: "/institutes/cohorts"},
1813
+ secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
1814
+ type: "engagement",
1815
+ name: "Sign-off workflow",
1816
+ recipient: "staff",
1817
+ group: "Work experience",
1818
+ },
1819
+ engagementWexActivate: {
1820
+ product: "admin",
1821
+ description: "Asks the school to activate the students in their work experience group.",
1822
+ params: [...engagementEmailParams],
1823
+ button: {text: "Activate students", link: "/institutes/students"},
1824
+ secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
1825
+ type: "engagement",
1826
+ name: "Activate students",
1827
+ recipient: "staff",
1828
+ group: "Work experience",
1829
+ },
1830
+ engagementWexPlacements: {
1831
+ product: "admin",
1832
+ description: "Suggests adding the first placement.",
1833
+ params: [...engagementEmailParams],
1834
+ button: {text: "Add a placement", link: "/institutes/cohorts"},
1835
+ secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
1836
+ type: "engagement",
1837
+ name: "First placements",
1838
+ recipient: "staff",
1839
+ group: "Work experience",
1840
+ },
1841
+ engagementWexFeedback: {
1842
+ product: "admin",
1843
+ description: "Suggests turning on feedback at the end of each placement.",
1844
+ params: [...engagementEmailParams],
1845
+ button: {text: "Set up feedback", link: "/institutes/cohorts"},
1846
+ secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
1847
+ type: "engagement",
1848
+ name: "Placement feedback",
1849
+ recipient: "staff",
1850
+ group: "Work experience",
1851
+ },
1852
+ engagementHubFirstEvent: {
1853
+ product: "admin",
1854
+ description: "Suggests a careers hub school requests its first employer event.",
1855
+ params: [...engagementEmailParams, "hubName"],
1856
+ button: {text: "Request an event", link: "/institutes/events"},
1857
+ type: "engagement",
1858
+ name: "Hub school first event",
1859
+ recipient: "staff",
1860
+ group: "Hub schools",
1861
+ },
1862
+ engagementReminderDay7: {
1863
+ product: "admin",
1864
+ description: "A week after any onboarding email, if the step still isn't done.",
1865
+ params: [...engagementEmailParams, "featureName", "featureDescription", "featurePath"],
1866
+ button: {text: "Get started", link: "/institutes{{featurePath}}"},
1867
+ type: "engagement",
1868
+ name: "Reminder: day 7",
1869
+ recipient: "staff",
1870
+ group: "Reminders",
1871
+ },
1872
+ engagementReminderDay7Plan: {
1873
+ product: "admin",
1874
+ description: "Day 7 reminder for a feature on the plan board, with a link to pick a different term.",
1875
+ params: [...engagementEmailParams, "featureName", "featureDescription", "featurePath"],
1876
+ button: {text: "Get started", link: "/institutes{{featurePath}}"},
1877
+ secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
1878
+ type: "engagement",
1879
+ name: "Reminder: day 7 (plan feature)",
1880
+ recipient: "staff",
1881
+ group: "Reminders",
1882
+ },
1883
+ engagementReminderDay21: {
1884
+ product: "admin",
1885
+ description: "Three weeks after any onboarding email, if the step still isn't done. Offers a call.",
1886
+ params: [...engagementEmailParams, "featureName", "featureSentence", "featurePath"],
1887
+ button: {text: "Get started", link: "/institutes{{featurePath}}"},
1888
+ type: "engagement",
1889
+ name: "Reminder: day 21",
1890
+ recipient: "staff",
1891
+ group: "Reminders",
1892
+ },
1893
+ engagementReminderDay21Plan: {
1894
+ product: "admin",
1895
+ description: "Day 21 reminder for a feature on the plan board: do it now or pick a term.",
1896
+ params: [...engagementEmailParams, "featureName", "featureSentence", "featurePath"],
1897
+ button: {text: "Get started", link: "/institutes{{featurePath}}"},
1898
+ secondaryButton: {text: "Pick a different term", link: "/plan/{{planCode}}"},
1899
+ type: "engagement",
1900
+ name: "Reminder: day 21 (plan feature)",
1901
+ recipient: "staff",
1902
+ group: "Reminders",
1903
+ },
1904
+ engagementRenewalPrice: {
1905
+ product: "admin",
1906
+ description: "Gives next year's price in time for the school's budget.",
1907
+ params: [...engagementEmailParams, "renewalDate", "quote"],
1908
+ button: {text: "See plan and invoices", link: "/institutes/organisation/billing"},
1909
+ type: "engagement",
1910
+ name: "Next year's price",
1911
+ recipient: "staff",
1912
+ group: "Renewals",
1913
+ },
1914
+ engagementRenewalReview: {
1915
+ product: "admin",
1916
+ description: "Before the summer: thanks the school and points to the impact report.",
1917
+ params: [...engagementEmailParams],
1918
+ button: {text: "Get your impact report", link: "/institutes/organisation/overview"},
1919
+ type: "engagement",
1920
+ name: "Year in review",
1921
+ recipient: "staff",
1922
+ group: "Renewals",
1923
+ },
1924
+ engagementRenewalDue: {
1925
+ product: "admin",
1926
+ description: "60 days before renewal: the date and next year's price.",
1927
+ params: [...engagementEmailParams, "renewalDate", "quote"],
1928
+ button: {text: "See your plan", link: "/institutes/organisation/billing"},
1929
+ type: "engagement",
1930
+ name: "60 days out",
1931
+ recipient: "staff",
1932
+ group: "Renewals",
1933
+ },
1934
+ engagementRenewalInvoice: {
1935
+ product: "admin",
1936
+ description: "Asks whether the renewal invoice needs a purchase order number.",
1937
+ params: [...engagementEmailParams, "renewalDate", "quote"],
1938
+ button: {text: "See your invoices", link: "/institutes/organisation/billing"},
1939
+ type: "engagement",
1940
+ name: "Invoice or PO",
1941
+ recipient: "staff",
1942
+ group: "Renewals",
1943
+ },
1944
+ engagementRenewalThanks: {
1945
+ product: "admin",
1946
+ description: "Sent once the school has renewed.",
1947
+ params: [...engagementEmailParams],
1948
+ button: {text: "Open CareerThread", link: "/institutes/home"},
1949
+ type: "engagement",
1950
+ name: "Thanks for renewing",
1951
+ recipient: "staff",
1952
+ group: "Renewals",
1953
+ },
1708
1954
  }
1709
1955
 
1710
1956
  // Whether a resource tagged with a product belongs to a product this organisation has. Untagged, or no product info, shows.
@@ -1720,7 +1966,10 @@ export function productTagAvailable(tag: ProductTag|undefined, products?: Resolv
1720
1966
  export function emailTemplateAvailable(templateKey: string|undefined, products?: ResolvedProducts) {
1721
1967
  // A saved template whose key has since been removed from emailTemplates is never sent, so hide it.
1722
1968
  if (templateKey && !emailTemplates[templateKey]) return false;
1723
- return productTagAvailable(templateKey ? emailTemplates[templateKey]?.product : undefined, products);
1969
+ const product = templateKey ? emailTemplates[templateKey].product : undefined;
1970
+ // CareerThread's own emails: only when there's no product info, which is an admin looking.
1971
+ if (product === "admin") return !products;
1972
+ return productTagAvailable(product, products);
1724
1973
  }
1725
1974
 
1726
1975
  export const productTagLabels: {[k in ProductTag]: string} = {
@@ -1736,6 +1985,11 @@ export const productTagLabels: {[k in ProductTag]: string} = {
1736
1985
  hub: "Careers hub",
1737
1986
  };
1738
1987
 
1988
+ export const emailTemplateProductLabels: {[k in EmailTemplateProduct]: string} = {
1989
+ ...productTagLabels,
1990
+ admin: "CareerThread admin",
1991
+ };
1992
+
1739
1993
  // Example values for previewing email templates, so customers see a filled-in email
1740
1994
  // rather than {{placeholders}}. Shared values first, then per-template overrides where
1741
1995
  // the same param means something different.
@@ -5,7 +5,8 @@ import {InstituteData, StudentGroup} from "./typeDefinitions";
5
5
  import {yearGroupStudentGroup} from "./studentGroups";
6
6
  import {resolveProducts} from "./productResolver";
7
7
  import {SurveyGroupScope} from "./surveys/types";
8
- import {BREVO_TEMPLATE_UNSET, FEATURE_MARKETING} from "./featureMarketing";
8
+ import {FEATURE_MARKETING} from "./featureMarketing";
9
+ import {emailTemplates} from "./constants";
9
10
  import {
10
11
  ONBOARDING_PATHWAYS,
11
12
  OnboardingContext,
@@ -14,6 +15,8 @@ import {
14
15
  evaluateOnboarding,
15
16
  nextOnboardingStep,
16
17
  onboardingCountsNeeded,
18
+ onboardingStepDone,
19
+ onboardingStepOfTemplate,
17
20
  } from "./onboardingPathways";
18
21
 
19
22
  /** A year group launched for one academic year — what every launch config holds. */
@@ -52,14 +55,26 @@ describe("registry", () => {
52
55
  expect(new Set(keys).size).toBe(keys.length);
53
56
  });
54
57
 
55
- it("gives every step a setup template slot, and every yearly step a resend one", () => {
58
+ it("points every step email at an admin email template, with a resend only on yearly steps", () => {
56
59
  ONBOARDING_PATHWAYS.forEach((p) => p.steps.forEach((s) => {
57
- expect(typeof s.templateIds.setup).toBe("number");
58
- if (s.thisYear) expect(typeof s.templateIds.resend).toBe("number");
59
- else expect(s.templateIds.resend).toBeUndefined();
60
+ if (!s.emailTemplates) return;
61
+ expect(emailTemplates[s.emailTemplates.setup]?.product).toBe("admin");
62
+ if (s.emailTemplates.resend) {
63
+ expect(s.thisYear).toBeDefined();
64
+ expect(emailTemplates[s.emailTemplates.resend]?.product).toBe("admin");
65
+ }
60
66
  }));
61
67
  });
62
68
 
69
+ it("finds the step for a template, and judges it done on its own", () => {
70
+ expect(onboardingStepOfTemplate("engagementWexCohort")?.step.key).toBe("wex.cohort");
71
+ expect(onboardingStepOfTemplate("engagementRenewalThanks")).toBeUndefined();
72
+ // The Wonde step doesn't apply to a spreadsheet school, but students still finish it.
73
+ expect(onboardingStepDone(ctx(school(), {students: 0}), "students.wonde")).toBe(false);
74
+ expect(onboardingStepDone(ctx(school(), {students: 30}), "students.wonde")).toBe(true);
75
+ expect(onboardingStepDone(ctx(school()), "nope")).toBeUndefined();
76
+ });
77
+
63
78
  it("starts every marketed feature's pathway with its marketing detector", () => {
64
79
  // The shared-detector promise: the day marketing stops pitching a feature
65
80
  // is the day onboarding starts nudging the step after it.
@@ -140,7 +155,7 @@ describe("progress", () => {
140
155
  expect(step?.key).toBe("activities.launch");
141
156
  expect(step?.state).toBe("yearMissing");
142
157
  expect(step?.variant).toBe("resend");
143
- expect(step?.templateId).toBe(BREVO_TEMPLATE_UNSET);
158
+ expect(step?.emailTemplate).toBeUndefined();
144
159
  expect(pathway(ctx(school()), "activities").next?.state).toBe("unconfigured");
145
160
  const live = school({activityLoggingConfig: {
146
161
  setupCompletedAt: "2026-09-02", groups: [launchedYearGroup("Year 9", "2026-27")],