backend-manager 5.0.184 → 5.0.186

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/CHANGELOG.md CHANGED
@@ -14,6 +14,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ # [5.0.186] - 2026-04-01
18
+ ### Fixed
19
+ - Move markdown rendering and UTM link tagging to run after `_.merge()` so caller overrides to `body.message` and `email.body` are properly processed
20
+
21
+ # [5.0.185] - 2026-04-01
22
+ ### Changed
23
+ - Use `_.merge` for dynamic template data so callers can override any nested field (e.g. `email.preview`, `personalization.name`, `data.body.*`)
24
+ - Set email schema `template` default to `'default'` instead of `undefined`
25
+
17
26
  # [5.0.184] - 2026-03-31
18
27
  ### Changed
19
28
  - Renamed email template shortcuts from `main/` to `core/` prefix across constants and all consumer files
package/TODO-2.md CHANGED
@@ -10,10 +10,26 @@ payments/reactivate
10
10
  payments/upgrade
11
11
  * takes a subscription id and a new plan id and upgrades the user's subscription to the new plan. this can only be done if the user has an active subscription.
12
12
 
13
+ I'd call that cancellation-reversed — it pairs naturally with the existing cancellation-requested transition and clearly communicates the action (they reversed/undid the pending cancellation).
14
+
15
+ on the email we send, for payment recovered what is the category
16
+ Let me check the handler.
17
+
18
+ Read payment-recovered.js
19
+ The category is order/payment-recovered (line 13).
20
+
21
+ TODO-2.md
22
+ waht about when they request a cancel
23
+ Read cancellation-requested.js
24
+ The category is order/cancellation-requested (line 13).
25
+
13
26
  ---
14
27
  GHOSTII REVAMP
15
28
  * better logic for generating posts. better model? claude?
16
29
 
30
+ ---- MCP
31
+ * ability for consuming prjec to specify MCP functions
32
+
17
33
  -------
18
34
  UPSELL
19
35
  * products in BEM can have an UPSELL where you link another product ID and it allows you to add it to your cart OR shows you after checkout?
@@ -0,0 +1,14 @@
1
+ https://github.com/disposable-email-domains/disposable-email-domains?tab=readme-ov-file
2
+ https://www.npmjs.com/package/disposable-domains
3
+ https://github.com/tompec/disposable-email-domains
4
+
5
+ Two repos:
6
+
7
+ Repo Domains Approach
8
+ disposable-email-domains/disposable-email-domains 5,359 Curated, conservative, high confidence
9
+ ivolo/disposable-email-domains 121,569 Aggressive, aggregated from many sources, more false positives
10
+ Our current list has 854 — so even the smaller curated list is 6x larger.
11
+
12
+ For our use case (currently only used to skip marketing sync, not blocking signups), I'd recommend the 5,359 curated list — it's comprehensive enough without being overly aggressive. And if we ever do use it for blocking signups, the false positive risk is much lower.
13
+
14
+ Want to swap to that one?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.184",
3
+ "version": "5.0.186",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -188,55 +188,60 @@ Transactional.prototype.build = async function (settings) {
188
188
  signoff.urlText = signoff.urlText || '@ianwieds';
189
189
  }
190
190
 
191
- // Process markdown in body fields
192
- if (settings?.data?.body?.message) {
193
- settings.data.body.message = md.render(settings.data.body.message);
194
- }
195
- if (settings?.data?.email?.body) {
196
- settings.data.email.body = md.render(settings.data.email.body);
197
- }
198
-
199
- // Tag links with UTM params for attribution
200
- const utmOptions = {
201
- brandUrl: brand?.url,
202
- brandId: brand?.id,
203
- campaign: settings.sender || templateId,
204
- type: 'transactional',
205
- utm: settings.utm,
206
- };
207
-
208
- if (settings?.data?.body?.message) {
209
- settings.data.body.message = tagLinks(settings.data.body.message, utmOptions);
210
- }
211
- if (settings?.data?.email?.body) {
212
- settings.data.email.body = tagLinks(settings.data.email.body, utmOptions);
213
- }
214
-
215
- // Build dynamic template data
191
+ // Build dynamic template data defaults
216
192
  const dynamicTemplateData = {
217
193
  email: {
218
194
  id: Manager.require('uuid').v4(),
219
- subject: settings?.data?.email?.subject || subject,
220
- preview: settings?.data?.email?.preview || null,
221
- body: settings?.data?.email?.body || null,
195
+ subject,
196
+ preview: null,
197
+ body: null,
222
198
  unsubscribeUrl,
223
199
  categories,
224
200
  footer: {
225
- text: settings?.data?.email?.footer?.text || null,
201
+ text: null,
226
202
  },
227
203
  carbonCopy: copy,
228
204
  },
229
205
  personalization: {
230
206
  email: to[0].email,
231
207
  name: to[0].name,
232
- ...settings?.data?.personalization,
233
208
  },
234
209
  signoff,
235
210
  brand: brandData,
236
211
  user: userProperties,
237
- data: settings.data || {},
212
+ data: {},
213
+ };
214
+
215
+ // Deep-merge caller's data on top so they can override any field
216
+ // (e.g. email.preview, email.subject, personalization.name, data.body.*, etc.)
217
+ if (settings.data) {
218
+ _.merge(dynamicTemplateData, settings.data);
219
+ }
220
+
221
+ // Process markdown in body fields (after merge so all data paths are resolved)
222
+ if (dynamicTemplateData.data?.body?.message) {
223
+ dynamicTemplateData.data.body.message = md.render(dynamicTemplateData.data.body.message);
224
+ }
225
+ if (dynamicTemplateData.email?.body) {
226
+ dynamicTemplateData.email.body = md.render(dynamicTemplateData.email.body);
227
+ }
228
+
229
+ // Tag links with UTM params for attribution
230
+ const utmOptions = {
231
+ brandUrl: brand?.url,
232
+ brandId: brand?.id,
233
+ campaign: settings.sender || templateId,
234
+ type: 'transactional',
235
+ utm: settings.utm,
238
236
  };
239
237
 
238
+ if (dynamicTemplateData.data?.body?.message) {
239
+ dynamicTemplateData.data.body.message = tagLinks(dynamicTemplateData.data.body.message, utmOptions);
240
+ }
241
+ if (dynamicTemplateData.email?.body) {
242
+ dynamicTemplateData.email.body = tagLinks(dynamicTemplateData.email.body, utmOptions);
243
+ }
244
+
240
245
  // Build the email object
241
246
  const email = {
242
247
  to,
@@ -15,7 +15,7 @@ module.exports = () => ({
15
15
  replyTo: { types: ['string'], default: undefined },
16
16
  sender: { types: ['string'], default: undefined },
17
17
  subject: { types: ['string'], default: undefined },
18
- template: { types: ['string'], default: undefined },
18
+ template: { types: ['string'], default: 'default' },
19
19
  group: { types: ['number', 'string'], default: undefined },
20
20
  sendAt: { types: ['number', 'string'], default: undefined },
21
21
  data: { types: ['object'], default: {} },