@drawbridge/drawbridge-utils 0.0.104 → 0.0.106

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/dist/features.cjs CHANGED
@@ -99,11 +99,6 @@ var connection = {
99
99
  error: "Plan does not include Mailchimp connection",
100
100
  feature: "Mailchimp connection"
101
101
  },
102
- sender: {
103
- key: "organization:connection:sender",
104
- error: "Plan does not include a verified sending domain",
105
- feature: "Sending domain connection"
106
- },
107
102
  sendgrid: {
108
103
  key: "organization:connection:sendgrid",
109
104
  error: "Plan does not include SendGrid connection",
@@ -168,6 +163,19 @@ var organization = {
168
163
  error: "Your plan does not include brands",
169
164
  feature: "Organization brands management"
170
165
  },
166
+ // Gates the Networking section as a whole — a verified sending domain today,
167
+ // the SMS number and a custom page domain as they land. One key rather than
168
+ // one per type: they are the same capability to a merchant, and splitting
169
+ // them would mean a plan could grant half a section.
170
+ //
171
+ // It replaces `connection.sender`, which named a connection this stopped
172
+ // being. Free organizations cannot send lead-facing email at all, so a
173
+ // sending identity there is one they could never send from.
174
+ networking: {
175
+ key: "organization:networking",
176
+ error: "Your plan does not include a custom sending identity",
177
+ feature: "Organization networking"
178
+ },
171
179
  members: {
172
180
  key: "organization:members",
173
181
  error: "Your plan does not include team members",
@@ -73,11 +73,6 @@ const connection = {
73
73
  error : 'Plan does not include Mailchimp connection',
74
74
  feature : 'Mailchimp connection'
75
75
  },
76
- sender : {
77
- key : 'organization:connection:sender',
78
- error : 'Plan does not include a verified sending domain',
79
- feature : 'Sending domain connection'
80
- },
81
76
  sendgrid : {
82
77
  key : 'organization:connection:sendgrid',
83
78
  error : 'Plan does not include SendGrid connection',
@@ -144,6 +139,19 @@ const organization = {
144
139
  error : 'Your plan does not include brands',
145
140
  feature : 'Organization brands management',
146
141
  },
142
+ // Gates the Networking section as a whole — a verified sending domain today,
143
+ // the SMS number and a custom page domain as they land. One key rather than
144
+ // one per type: they are the same capability to a merchant, and splitting
145
+ // them would mean a plan could grant half a section.
146
+ //
147
+ // It replaces `connection.sender`, which named a connection this stopped
148
+ // being. Free organizations cannot send lead-facing email at all, so a
149
+ // sending identity there is one they could never send from.
150
+ networking : {
151
+ key : 'organization:networking',
152
+ error : 'Your plan does not include a custom sending identity',
153
+ feature : 'Organization networking'
154
+ },
147
155
  members : {
148
156
  key : 'organization:members',
149
157
  error : 'Your plan does not include team members',
@@ -73,11 +73,6 @@ const connection = {
73
73
  error : 'Plan does not include Mailchimp connection',
74
74
  feature : 'Mailchimp connection'
75
75
  },
76
- sender : {
77
- key : 'organization:connection:sender',
78
- error : 'Plan does not include a verified sending domain',
79
- feature : 'Sending domain connection'
80
- },
81
76
  sendgrid : {
82
77
  key : 'organization:connection:sendgrid',
83
78
  error : 'Plan does not include SendGrid connection',
@@ -144,6 +139,19 @@ const organization = {
144
139
  error : 'Your plan does not include brands',
145
140
  feature : 'Organization brands management',
146
141
  },
142
+ // Gates the Networking section as a whole — a verified sending domain today,
143
+ // the SMS number and a custom page domain as they land. One key rather than
144
+ // one per type: they are the same capability to a merchant, and splitting
145
+ // them would mean a plan could grant half a section.
146
+ //
147
+ // It replaces `connection.sender`, which named a connection this stopped
148
+ // being. Free organizations cannot send lead-facing email at all, so a
149
+ // sending identity there is one they could never send from.
150
+ networking : {
151
+ key : 'organization:networking',
152
+ error : 'Your plan does not include a custom sending identity',
153
+ feature : 'Organization networking'
154
+ },
147
155
  members : {
148
156
  key : 'organization:members',
149
157
  error : 'Your plan does not include team members',
package/dist/features.js CHANGED
@@ -71,11 +71,6 @@ var connection = {
71
71
  error: "Plan does not include Mailchimp connection",
72
72
  feature: "Mailchimp connection"
73
73
  },
74
- sender: {
75
- key: "organization:connection:sender",
76
- error: "Plan does not include a verified sending domain",
77
- feature: "Sending domain connection"
78
- },
79
74
  sendgrid: {
80
75
  key: "organization:connection:sendgrid",
81
76
  error: "Plan does not include SendGrid connection",
@@ -140,6 +135,19 @@ var organization = {
140
135
  error: "Your plan does not include brands",
141
136
  feature: "Organization brands management"
142
137
  },
138
+ // Gates the Networking section as a whole — a verified sending domain today,
139
+ // the SMS number and a custom page domain as they land. One key rather than
140
+ // one per type: they are the same capability to a merchant, and splitting
141
+ // them would mean a plan could grant half a section.
142
+ //
143
+ // It replaces `connection.sender`, which named a connection this stopped
144
+ // being. Free organizations cannot send lead-facing email at all, so a
145
+ // sending identity there is one they could never send from.
146
+ networking: {
147
+ key: "organization:networking",
148
+ error: "Your plan does not include a custom sending identity",
149
+ feature: "Organization networking"
150
+ },
143
151
  members: {
144
152
  key: "organization:members",
145
153
  error: "Your plan does not include team members",
package/dist/plans.cjs CHANGED
@@ -110,11 +110,6 @@ var connection = {
110
110
  error: "Plan does not include Mailchimp connection",
111
111
  feature: "Mailchimp connection"
112
112
  },
113
- sender: {
114
- key: "organization:connection:sender",
115
- error: "Plan does not include a verified sending domain",
116
- feature: "Sending domain connection"
117
- },
118
113
  sendgrid: {
119
114
  key: "organization:connection:sendgrid",
120
115
  error: "Plan does not include SendGrid connection",
@@ -157,6 +152,19 @@ var organization = {
157
152
  error: "Your plan does not include brands",
158
153
  feature: "Organization brands management"
159
154
  },
155
+ // Gates the Networking section as a whole — a verified sending domain today,
156
+ // the SMS number and a custom page domain as they land. One key rather than
157
+ // one per type: they are the same capability to a merchant, and splitting
158
+ // them would mean a plan could grant half a section.
159
+ //
160
+ // It replaces `connection.sender`, which named a connection this stopped
161
+ // being. Free organizations cannot send lead-facing email at all, so a
162
+ // sending identity there is one they could never send from.
163
+ networking: {
164
+ key: "organization:networking",
165
+ error: "Your plan does not include a custom sending identity",
166
+ feature: "Organization networking"
167
+ },
160
168
  members: {
161
169
  key: "organization:members",
162
170
  error: "Your plan does not include team members",
@@ -341,7 +349,7 @@ var plans = {
341
349
  // lead-facing email at all (the send path gates on an active
342
350
  // subscription), so granting it there would offer a domain that can
343
351
  // never send from.
344
- features: all.features([connection.sender.key, organization.members.key]),
352
+ features: all.features([organization.networking.key, organization.members.key]),
345
353
  limits: all.limits({ actions: 5e3, members: 3, storage: gigabyte * 10 }),
346
354
  marketing: {
347
355
  description: "Tools to fine-tune campaigns and improve lead quality.",
@@ -362,7 +370,7 @@ var plans = {
362
370
  },
363
371
  DB00003: {
364
372
  features: all.features([
365
- connection.sender.key,
373
+ organization.networking.key,
366
374
  organization.advertisements.key,
367
375
  organization.analytics.key,
368
376
  organization.members.key,
@@ -393,7 +401,7 @@ var plans = {
393
401
  },
394
402
  DB00004: {
395
403
  features: all.features([
396
- connection.sender.key,
404
+ organization.networking.key,
397
405
  organization.advertisements.key,
398
406
  organization.analytics.key,
399
407
  organization.members.key,
@@ -424,7 +432,7 @@ var plans = {
424
432
  },
425
433
  DB00005: {
426
434
  features: all.features([
427
- connection.sender.key,
435
+ organization.networking.key,
428
436
  organization.advertisements.key,
429
437
  organization.analytics.key,
430
438
  organization.members.key,
@@ -469,7 +477,7 @@ var resolvePlan = (subscription) => {
469
477
  // baseline whether or not the deal thought to name it. Today that is the
470
478
  // sending domain: every catalog paid tier grants it, and a custom plan
471
479
  // silently lacking it would be a support ticket, not a pricing decision.
472
- features: all.features([connection.sender.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
480
+ features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
473
481
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
474
482
  // A custom plan stores its overage BARE on `custom.overages` — a different
475
483
  // shape from the catalog's nested one. Number() so a deal stored as a string
package/dist/plans.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { connection, organization, page, fields, field } from './features.cjs';
1
+ import { organization, page, connection, fields, field } from './features.cjs';
2
2
  import { infinite, gigabyte } from './index.cjs';
3
3
  import 'currency-codes';
4
4
  import 'nanoid';
@@ -131,7 +131,7 @@ const plans = {
131
131
  // lead-facing email at all (the send path gates on an active
132
132
  // subscription), so granting it there would offer a domain that can
133
133
  // never send from.
134
- features : all.features([ connection.sender.key, organization.members.key ]),
134
+ features : all.features([ organization.networking.key, organization.members.key ]),
135
135
  limits : all.limits({ actions : 5000, members : 3, storage : gigabyte * 10 }),
136
136
  marketing : {
137
137
  description : 'Tools to fine-tune campaigns and improve lead quality.',
@@ -152,7 +152,7 @@ const plans = {
152
152
  },
153
153
  DB00003 : {
154
154
  features : all.features([
155
- connection.sender.key,
155
+ organization.networking.key,
156
156
  organization.advertisements.key,
157
157
  organization.analytics.key,
158
158
  organization.members.key,
@@ -183,7 +183,7 @@ const plans = {
183
183
  },
184
184
  DB00004 : {
185
185
  features : all.features([
186
- connection.sender.key,
186
+ organization.networking.key,
187
187
  organization.advertisements.key,
188
188
  organization.analytics.key,
189
189
  organization.members.key,
@@ -214,7 +214,7 @@ const plans = {
214
214
  },
215
215
  DB00005 : {
216
216
  features : all.features([
217
- connection.sender.key,
217
+ organization.networking.key,
218
218
  organization.advertisements.key,
219
219
  organization.analytics.key,
220
220
  organization.members.key,
@@ -275,7 +275,7 @@ const resolvePlan = ( subscription ) => {
275
275
  // baseline whether or not the deal thought to name it. Today that is the
276
276
  // sending domain: every catalog paid tier grants it, and a custom plan
277
277
  // silently lacking it would be a support ticket, not a pricing decision.
278
- features : all.features([ connection.sender.key, ...( custom.features?.granted || [] ) ]),
278
+ features : all.features([ organization.networking.key, ...( custom.features?.granted || [] ) ]),
279
279
  limits : all.limits( custom.limits?.organization || {} ),
280
280
  // A custom plan stores its overage BARE on `custom.overages` — a different
281
281
  // shape from the catalog's nested one. Number() so a deal stored as a string
package/dist/plans.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { connection, organization, page, fields, field } from './features.js';
1
+ import { organization, page, connection, fields, field } from './features.js';
2
2
  import { infinite, gigabyte } from './index.js';
3
3
  import 'currency-codes';
4
4
  import 'nanoid';
@@ -131,7 +131,7 @@ const plans = {
131
131
  // lead-facing email at all (the send path gates on an active
132
132
  // subscription), so granting it there would offer a domain that can
133
133
  // never send from.
134
- features : all.features([ connection.sender.key, organization.members.key ]),
134
+ features : all.features([ organization.networking.key, organization.members.key ]),
135
135
  limits : all.limits({ actions : 5000, members : 3, storage : gigabyte * 10 }),
136
136
  marketing : {
137
137
  description : 'Tools to fine-tune campaigns and improve lead quality.',
@@ -152,7 +152,7 @@ const plans = {
152
152
  },
153
153
  DB00003 : {
154
154
  features : all.features([
155
- connection.sender.key,
155
+ organization.networking.key,
156
156
  organization.advertisements.key,
157
157
  organization.analytics.key,
158
158
  organization.members.key,
@@ -183,7 +183,7 @@ const plans = {
183
183
  },
184
184
  DB00004 : {
185
185
  features : all.features([
186
- connection.sender.key,
186
+ organization.networking.key,
187
187
  organization.advertisements.key,
188
188
  organization.analytics.key,
189
189
  organization.members.key,
@@ -214,7 +214,7 @@ const plans = {
214
214
  },
215
215
  DB00005 : {
216
216
  features : all.features([
217
- connection.sender.key,
217
+ organization.networking.key,
218
218
  organization.advertisements.key,
219
219
  organization.analytics.key,
220
220
  organization.members.key,
@@ -275,7 +275,7 @@ const resolvePlan = ( subscription ) => {
275
275
  // baseline whether or not the deal thought to name it. Today that is the
276
276
  // sending domain: every catalog paid tier grants it, and a custom plan
277
277
  // silently lacking it would be a support ticket, not a pricing decision.
278
- features : all.features([ connection.sender.key, ...( custom.features?.granted || [] ) ]),
278
+ features : all.features([ organization.networking.key, ...( custom.features?.granted || [] ) ]),
279
279
  limits : all.limits( custom.limits?.organization || {} ),
280
280
  // A custom plan stores its overage BARE on `custom.overages` — a different
281
281
  // shape from the catalog's nested one. Number() so a deal stored as a string
package/dist/plans.js CHANGED
@@ -71,11 +71,6 @@ var connection = {
71
71
  error: "Plan does not include Mailchimp connection",
72
72
  feature: "Mailchimp connection"
73
73
  },
74
- sender: {
75
- key: "organization:connection:sender",
76
- error: "Plan does not include a verified sending domain",
77
- feature: "Sending domain connection"
78
- },
79
74
  sendgrid: {
80
75
  key: "organization:connection:sendgrid",
81
76
  error: "Plan does not include SendGrid connection",
@@ -118,6 +113,19 @@ var organization = {
118
113
  error: "Your plan does not include brands",
119
114
  feature: "Organization brands management"
120
115
  },
116
+ // Gates the Networking section as a whole — a verified sending domain today,
117
+ // the SMS number and a custom page domain as they land. One key rather than
118
+ // one per type: they are the same capability to a merchant, and splitting
119
+ // them would mean a plan could grant half a section.
120
+ //
121
+ // It replaces `connection.sender`, which named a connection this stopped
122
+ // being. Free organizations cannot send lead-facing email at all, so a
123
+ // sending identity there is one they could never send from.
124
+ networking: {
125
+ key: "organization:networking",
126
+ error: "Your plan does not include a custom sending identity",
127
+ feature: "Organization networking"
128
+ },
121
129
  members: {
122
130
  key: "organization:members",
123
131
  error: "Your plan does not include team members",
@@ -302,7 +310,7 @@ var plans = {
302
310
  // lead-facing email at all (the send path gates on an active
303
311
  // subscription), so granting it there would offer a domain that can
304
312
  // never send from.
305
- features: all.features([connection.sender.key, organization.members.key]),
313
+ features: all.features([organization.networking.key, organization.members.key]),
306
314
  limits: all.limits({ actions: 5e3, members: 3, storage: gigabyte * 10 }),
307
315
  marketing: {
308
316
  description: "Tools to fine-tune campaigns and improve lead quality.",
@@ -323,7 +331,7 @@ var plans = {
323
331
  },
324
332
  DB00003: {
325
333
  features: all.features([
326
- connection.sender.key,
334
+ organization.networking.key,
327
335
  organization.advertisements.key,
328
336
  organization.analytics.key,
329
337
  organization.members.key,
@@ -354,7 +362,7 @@ var plans = {
354
362
  },
355
363
  DB00004: {
356
364
  features: all.features([
357
- connection.sender.key,
365
+ organization.networking.key,
358
366
  organization.advertisements.key,
359
367
  organization.analytics.key,
360
368
  organization.members.key,
@@ -385,7 +393,7 @@ var plans = {
385
393
  },
386
394
  DB00005: {
387
395
  features: all.features([
388
- connection.sender.key,
396
+ organization.networking.key,
389
397
  organization.advertisements.key,
390
398
  organization.analytics.key,
391
399
  organization.members.key,
@@ -430,7 +438,7 @@ var resolvePlan = (subscription) => {
430
438
  // baseline whether or not the deal thought to name it. Today that is the
431
439
  // sending domain: every catalog paid tier grants it, and a custom plan
432
440
  // silently lacking it would be a support ticket, not a pricing decision.
433
- features: all.features([connection.sender.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
441
+ features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
434
442
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
435
443
  // A custom plan stores its overage BARE on `custom.overages` — a different
436
444
  // shape from the catalog's nested one. Number() so a deal stored as a string
package/dist/pricing.cjs CHANGED
@@ -43,6 +43,7 @@ __export(pricing_exports, {
43
43
  plans: () => plans,
44
44
  resolvePlan: () => resolvePlan,
45
45
  scrape: () => scrape,
46
+ sending: () => sending,
46
47
  unitAmountDecimal: () => unitAmountDecimal
47
48
  });
48
49
  module.exports = __toCommonJS(pricing_exports);
@@ -120,11 +121,6 @@ var connection = {
120
121
  error: "Plan does not include Mailchimp connection",
121
122
  feature: "Mailchimp connection"
122
123
  },
123
- sender: {
124
- key: "organization:connection:sender",
125
- error: "Plan does not include a verified sending domain",
126
- feature: "Sending domain connection"
127
- },
128
124
  sendgrid: {
129
125
  key: "organization:connection:sendgrid",
130
126
  error: "Plan does not include SendGrid connection",
@@ -167,6 +163,19 @@ var organization = {
167
163
  error: "Your plan does not include brands",
168
164
  feature: "Organization brands management"
169
165
  },
166
+ // Gates the Networking section as a whole — a verified sending domain today,
167
+ // the SMS number and a custom page domain as they land. One key rather than
168
+ // one per type: they are the same capability to a merchant, and splitting
169
+ // them would mean a plan could grant half a section.
170
+ //
171
+ // It replaces `connection.sender`, which named a connection this stopped
172
+ // being. Free organizations cannot send lead-facing email at all, so a
173
+ // sending identity there is one they could never send from.
174
+ networking: {
175
+ key: "organization:networking",
176
+ error: "Your plan does not include a custom sending identity",
177
+ feature: "Organization networking"
178
+ },
170
179
  members: {
171
180
  key: "organization:members",
172
181
  error: "Your plan does not include team members",
@@ -351,7 +360,7 @@ var plans = {
351
360
  // lead-facing email at all (the send path gates on an active
352
361
  // subscription), so granting it there would offer a domain that can
353
362
  // never send from.
354
- features: all.features([connection.sender.key, organization.members.key]),
363
+ features: all.features([organization.networking.key, organization.members.key]),
355
364
  limits: all.limits({ actions: 5e3, members: 3, storage: gigabyte * 10 }),
356
365
  marketing: {
357
366
  description: "Tools to fine-tune campaigns and improve lead quality.",
@@ -372,7 +381,7 @@ var plans = {
372
381
  },
373
382
  DB00003: {
374
383
  features: all.features([
375
- connection.sender.key,
384
+ organization.networking.key,
376
385
  organization.advertisements.key,
377
386
  organization.analytics.key,
378
387
  organization.members.key,
@@ -403,7 +412,7 @@ var plans = {
403
412
  },
404
413
  DB00004: {
405
414
  features: all.features([
406
- connection.sender.key,
415
+ organization.networking.key,
407
416
  organization.advertisements.key,
408
417
  organization.analytics.key,
409
418
  organization.members.key,
@@ -434,7 +443,7 @@ var plans = {
434
443
  },
435
444
  DB00005: {
436
445
  features: all.features([
437
- connection.sender.key,
446
+ organization.networking.key,
438
447
  organization.advertisements.key,
439
448
  organization.analytics.key,
440
449
  organization.members.key,
@@ -479,7 +488,7 @@ var resolvePlan = (subscription) => {
479
488
  // baseline whether or not the deal thought to name it. Today that is the
480
489
  // sending domain: every catalog paid tier grants it, and a custom plan
481
490
  // silently lacking it would be a support ticket, not a pricing decision.
482
- features: all.features([connection.sender.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
491
+ features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
483
492
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
484
493
  // A custom plan stores its overage BARE on `custom.overages` — a different
485
494
  // shape from the catalog's nested one. Number() so a deal stored as a string
@@ -774,6 +783,76 @@ var scrape = {
774
783
  };
775
784
 
776
785
  // lib/pricing.js
786
+ var emailPlans = {
787
+ essentials50k: {
788
+ included: 5e4,
789
+ // The plan's own price is not shown in the console once you are on it —
790
+ // it reads "Your Current Plan" where the price would be. Left null rather
791
+ // than guessed; fill from an invoice. Nothing derives from it yet, and an
792
+ // invented figure would quietly become the basis of an upgrade decision.
793
+ monthly: null,
794
+ overageCents: 0.133,
795
+ title: "Essentials 50K"
796
+ },
797
+ essentials100k: {
798
+ included: 1e5,
799
+ monthly: 3495,
800
+ overageCents: 0.09,
801
+ title: "Essentials 100K"
802
+ },
803
+ pro100k: {
804
+ included: 1e5,
805
+ monthly: 8995,
806
+ overageCents: 0.11,
807
+ title: "Pro 100K"
808
+ },
809
+ pro300k: {
810
+ included: 3e5,
811
+ monthly: 24900,
812
+ overageCents: 0.091,
813
+ title: "Pro 300K"
814
+ },
815
+ pro700k: {
816
+ included: 7e5,
817
+ monthly: 49900,
818
+ overageCents: 0.078,
819
+ title: "Pro 700K"
820
+ },
821
+ pro1500k: {
822
+ included: 15e5,
823
+ monthly: 79900,
824
+ overageCents: 0.059,
825
+ title: "Pro 1.5 Million"
826
+ },
827
+ pro2500k: {
828
+ included: 25e5,
829
+ monthly: 109900,
830
+ overageCents: 0.047,
831
+ title: "Pro 2.5 Million"
832
+ }
833
+ };
834
+ var emailPlan = "essentials50k";
835
+ var sending = {
836
+ email: {
837
+ // Fraction of the included volume that raises the alarm. Far enough ahead
838
+ // to act on, high enough not to fire on ordinary growth.
839
+ //
840
+ // Crossing it is NOT an outage. The provider bills overage rather than
841
+ // stopping sends, so the alarm is a COST signal: past the included volume
842
+ // every email is charged at overageCents, and at some run rate the next
843
+ // plan up is cheaper than the overage. That is the decision it exists to
844
+ // prompt, and it is why nobody should be woken by it.
845
+ alertThreshold: 0.8,
846
+ // Emails per calendar month included, PLATFORM-WIDE and across every send
847
+ // path: lead-facing mail, workflow steps, and the sign-in codes and
848
+ // account mail that no plan allowance meters.
849
+ monthlyCeiling: emailPlans[emailPlan].included,
850
+ overageCents: emailPlans[emailPlan].overageCents,
851
+ plan: emailPlan,
852
+ plans: emailPlans,
853
+ title: emailPlans[emailPlan].title
854
+ }
855
+ };
777
856
  var channels = {
778
857
  email: {
779
858
  actionsPerSend: 1,
@@ -803,5 +882,6 @@ var channels = {
803
882
  plans,
804
883
  resolvePlan,
805
884
  scrape,
885
+ sending,
806
886
  unitAmountDecimal
807
887
  });
@@ -36,6 +36,11 @@ import '@drawbridge/drawbridge-telemetry';
36
36
  // channels.sms.actionsPerSegment ACTIONS per SMS segment.
37
37
  // channels.*.includedInAllowance whether the plan's included actions cover it.
38
38
  //
39
+ // ─── Provider capacity (owned here) ───────────────────────────────────────────
40
+ // sending.email.monthlyCeiling EMAILS per calendar month we have BOUGHT,
41
+ // platform-wide across every send path.
42
+ // sending.email.alertThreshold FRACTION of that ceiling which alarms.
43
+ //
39
44
  // ─── Scrape + AI (lib/billing.js) ─────────────────────────────────────────────
40
45
  // MARKUP MULTIPLIER on provider cost (1.3 = 30% margin).
41
46
  // STANDARD_RATE_PER_GB DOLLARS per GB, BrightData Scraping Browser.
@@ -53,6 +58,121 @@ import '@drawbridge/drawbridge-telemetry';
53
58
  // consumer of this file.
54
59
 
55
60
 
61
+ // What we have BOUGHT from the sending providers, against what the plans above
62
+ // SELL. Here rather than in an env because it is a rate like every other number
63
+ // in this file, and the whole point of this file is that you do not have to
64
+ // know which environment to look in to find one. Changing it is a publish and a
65
+ // pin bump, which is the right friction for a number that changes when a
66
+ // contract changes.
67
+ //
68
+ // READ THIS BEFORE RAISING A PLAN ALLOWANCE. The ceiling is PLATFORM-WIDE while
69
+ // every allowance above is PER ORGANIZATION, and an allowance can be spent
70
+ // entirely on email — so the two are denominated against very different things:
71
+ //
72
+ // Elite 100,000 = 200% of the whole platform's email ceiling
73
+ // Premium 40,000 = 80%
74
+ // Pro 15,000 = 30%
75
+ // Starter 5,000 = 10%
76
+ //
77
+ // As of 2026-08-30 we sell about 2x the email we buy, against real usage of
78
+ // ~622 sends a month — so this is a shape to watch, not a fire.
79
+ //
80
+ // Exceeding the included volume does NOT stop sends. The provider bills
81
+ // overage per email (see overageCents), so the consequence is a bill and the
82
+ // question is only ever "is the next plan up cheaper than the overage yet".
83
+ // Both halves stay comfortable: at 0.133c of overage against 1.5-2.5c of
84
+ // action overage, email we send past the plan still earns more than ten times
85
+ // what it costs.
86
+ //
87
+ // Raising a tier's allowance should still move the plan below, or the included
88
+ // volume quietly becomes the number that decides the bill.
89
+ // The provider's plan ladder, so the ceiling is DERIVED from which plan we are
90
+ // on rather than being a loose number somebody has to remember to change. Read
91
+ // from the SendGrid Email API Plans page 2026-08-30.
92
+ //
93
+ // included EMAILS per calendar month before overage starts.
94
+ // overageCents CENTS per additional email. $0.00133 is 0.133 CENTS — the
95
+ // decimal is the whole reason this is not stored in dollars.
96
+ // monthly CENTS per month for the plan itself.
97
+ //
98
+ // Pro is not "cheaper email" — Pro 100K costs more per overage email than
99
+ // Essentials 100K. What Pro buys is a dedicated IP, subusers, regional data
100
+ // residency, and link branding. Move for those, not for the rate.
101
+ const emailPlans = {
102
+ essentials50k : {
103
+ included : 50000,
104
+ // The plan's own price is not shown in the console once you are on it —
105
+ // it reads "Your Current Plan" where the price would be. Left null rather
106
+ // than guessed; fill from an invoice. Nothing derives from it yet, and an
107
+ // invented figure would quietly become the basis of an upgrade decision.
108
+ monthly : null,
109
+ overageCents : 0.133,
110
+ title : 'Essentials 50K'
111
+ },
112
+ essentials100k : {
113
+ included : 100000,
114
+ monthly : 3495,
115
+ overageCents : 0.09,
116
+ title : 'Essentials 100K'
117
+ },
118
+ pro100k : {
119
+ included : 100000,
120
+ monthly : 8995,
121
+ overageCents : 0.11,
122
+ title : 'Pro 100K'
123
+ },
124
+ pro300k : {
125
+ included : 300000,
126
+ monthly : 24900,
127
+ overageCents : 0.091,
128
+ title : 'Pro 300K'
129
+ },
130
+ pro700k : {
131
+ included : 700000,
132
+ monthly : 49900,
133
+ overageCents : 0.078,
134
+ title : 'Pro 700K'
135
+ },
136
+ pro1500k : {
137
+ included : 1500000,
138
+ monthly : 79900,
139
+ overageCents : 0.059,
140
+ title : 'Pro 1.5 Million'
141
+ },
142
+ pro2500k : {
143
+ included : 2500000,
144
+ monthly : 109900,
145
+ overageCents : 0.047,
146
+ title : 'Pro 2.5 Million'
147
+ }
148
+ };
149
+
150
+ // THE ONE FIELD TO CHANGE when the provider plan changes. Everything else here
151
+ // follows from it.
152
+ const emailPlan = 'essentials50k';
153
+
154
+ const sending = {
155
+ email : {
156
+ // Fraction of the included volume that raises the alarm. Far enough ahead
157
+ // to act on, high enough not to fire on ordinary growth.
158
+ //
159
+ // Crossing it is NOT an outage. The provider bills overage rather than
160
+ // stopping sends, so the alarm is a COST signal: past the included volume
161
+ // every email is charged at overageCents, and at some run rate the next
162
+ // plan up is cheaper than the overage. That is the decision it exists to
163
+ // prompt, and it is why nobody should be woken by it.
164
+ alertThreshold : 0.8,
165
+ // Emails per calendar month included, PLATFORM-WIDE and across every send
166
+ // path: lead-facing mail, workflow steps, and the sign-in codes and
167
+ // account mail that no plan allowance meters.
168
+ monthlyCeiling : emailPlans[ emailPlan ].included,
169
+ overageCents : emailPlans[ emailPlan ].overageCents,
170
+ plan : emailPlan,
171
+ plans : emailPlans,
172
+ title : emailPlans[ emailPlan ].title
173
+ }
174
+ };
175
+
56
176
  // Weighted actions: what one message of each channel costs against a plan.
57
177
  // Named per unit so a bare `2` can never be read as cents.
58
178
  const channels = {
@@ -69,4 +189,4 @@ const channels = {
69
189
  }
70
190
  };
71
191
 
72
- export { channels };
192
+ export { channels, sending };
package/dist/pricing.d.ts CHANGED
@@ -36,6 +36,11 @@ import '@drawbridge/drawbridge-telemetry';
36
36
  // channels.sms.actionsPerSegment ACTIONS per SMS segment.
37
37
  // channels.*.includedInAllowance whether the plan's included actions cover it.
38
38
  //
39
+ // ─── Provider capacity (owned here) ───────────────────────────────────────────
40
+ // sending.email.monthlyCeiling EMAILS per calendar month we have BOUGHT,
41
+ // platform-wide across every send path.
42
+ // sending.email.alertThreshold FRACTION of that ceiling which alarms.
43
+ //
39
44
  // ─── Scrape + AI (lib/billing.js) ─────────────────────────────────────────────
40
45
  // MARKUP MULTIPLIER on provider cost (1.3 = 30% margin).
41
46
  // STANDARD_RATE_PER_GB DOLLARS per GB, BrightData Scraping Browser.
@@ -53,6 +58,121 @@ import '@drawbridge/drawbridge-telemetry';
53
58
  // consumer of this file.
54
59
 
55
60
 
61
+ // What we have BOUGHT from the sending providers, against what the plans above
62
+ // SELL. Here rather than in an env because it is a rate like every other number
63
+ // in this file, and the whole point of this file is that you do not have to
64
+ // know which environment to look in to find one. Changing it is a publish and a
65
+ // pin bump, which is the right friction for a number that changes when a
66
+ // contract changes.
67
+ //
68
+ // READ THIS BEFORE RAISING A PLAN ALLOWANCE. The ceiling is PLATFORM-WIDE while
69
+ // every allowance above is PER ORGANIZATION, and an allowance can be spent
70
+ // entirely on email — so the two are denominated against very different things:
71
+ //
72
+ // Elite 100,000 = 200% of the whole platform's email ceiling
73
+ // Premium 40,000 = 80%
74
+ // Pro 15,000 = 30%
75
+ // Starter 5,000 = 10%
76
+ //
77
+ // As of 2026-08-30 we sell about 2x the email we buy, against real usage of
78
+ // ~622 sends a month — so this is a shape to watch, not a fire.
79
+ //
80
+ // Exceeding the included volume does NOT stop sends. The provider bills
81
+ // overage per email (see overageCents), so the consequence is a bill and the
82
+ // question is only ever "is the next plan up cheaper than the overage yet".
83
+ // Both halves stay comfortable: at 0.133c of overage against 1.5-2.5c of
84
+ // action overage, email we send past the plan still earns more than ten times
85
+ // what it costs.
86
+ //
87
+ // Raising a tier's allowance should still move the plan below, or the included
88
+ // volume quietly becomes the number that decides the bill.
89
+ // The provider's plan ladder, so the ceiling is DERIVED from which plan we are
90
+ // on rather than being a loose number somebody has to remember to change. Read
91
+ // from the SendGrid Email API Plans page 2026-08-30.
92
+ //
93
+ // included EMAILS per calendar month before overage starts.
94
+ // overageCents CENTS per additional email. $0.00133 is 0.133 CENTS — the
95
+ // decimal is the whole reason this is not stored in dollars.
96
+ // monthly CENTS per month for the plan itself.
97
+ //
98
+ // Pro is not "cheaper email" — Pro 100K costs more per overage email than
99
+ // Essentials 100K. What Pro buys is a dedicated IP, subusers, regional data
100
+ // residency, and link branding. Move for those, not for the rate.
101
+ const emailPlans = {
102
+ essentials50k : {
103
+ included : 50000,
104
+ // The plan's own price is not shown in the console once you are on it —
105
+ // it reads "Your Current Plan" where the price would be. Left null rather
106
+ // than guessed; fill from an invoice. Nothing derives from it yet, and an
107
+ // invented figure would quietly become the basis of an upgrade decision.
108
+ monthly : null,
109
+ overageCents : 0.133,
110
+ title : 'Essentials 50K'
111
+ },
112
+ essentials100k : {
113
+ included : 100000,
114
+ monthly : 3495,
115
+ overageCents : 0.09,
116
+ title : 'Essentials 100K'
117
+ },
118
+ pro100k : {
119
+ included : 100000,
120
+ monthly : 8995,
121
+ overageCents : 0.11,
122
+ title : 'Pro 100K'
123
+ },
124
+ pro300k : {
125
+ included : 300000,
126
+ monthly : 24900,
127
+ overageCents : 0.091,
128
+ title : 'Pro 300K'
129
+ },
130
+ pro700k : {
131
+ included : 700000,
132
+ monthly : 49900,
133
+ overageCents : 0.078,
134
+ title : 'Pro 700K'
135
+ },
136
+ pro1500k : {
137
+ included : 1500000,
138
+ monthly : 79900,
139
+ overageCents : 0.059,
140
+ title : 'Pro 1.5 Million'
141
+ },
142
+ pro2500k : {
143
+ included : 2500000,
144
+ monthly : 109900,
145
+ overageCents : 0.047,
146
+ title : 'Pro 2.5 Million'
147
+ }
148
+ };
149
+
150
+ // THE ONE FIELD TO CHANGE when the provider plan changes. Everything else here
151
+ // follows from it.
152
+ const emailPlan = 'essentials50k';
153
+
154
+ const sending = {
155
+ email : {
156
+ // Fraction of the included volume that raises the alarm. Far enough ahead
157
+ // to act on, high enough not to fire on ordinary growth.
158
+ //
159
+ // Crossing it is NOT an outage. The provider bills overage rather than
160
+ // stopping sends, so the alarm is a COST signal: past the included volume
161
+ // every email is charged at overageCents, and at some run rate the next
162
+ // plan up is cheaper than the overage. That is the decision it exists to
163
+ // prompt, and it is why nobody should be woken by it.
164
+ alertThreshold : 0.8,
165
+ // Emails per calendar month included, PLATFORM-WIDE and across every send
166
+ // path: lead-facing mail, workflow steps, and the sign-in codes and
167
+ // account mail that no plan allowance meters.
168
+ monthlyCeiling : emailPlans[ emailPlan ].included,
169
+ overageCents : emailPlans[ emailPlan ].overageCents,
170
+ plan : emailPlan,
171
+ plans : emailPlans,
172
+ title : emailPlans[ emailPlan ].title
173
+ }
174
+ };
175
+
56
176
  // Weighted actions: what one message of each channel costs against a plan.
57
177
  // Named per unit so a bare `2` can never be read as cents.
58
178
  const channels = {
@@ -69,4 +189,4 @@ const channels = {
69
189
  }
70
190
  };
71
191
 
72
- export { channels };
192
+ export { channels, sending };
package/dist/pricing.js CHANGED
@@ -71,11 +71,6 @@ var connection = {
71
71
  error: "Plan does not include Mailchimp connection",
72
72
  feature: "Mailchimp connection"
73
73
  },
74
- sender: {
75
- key: "organization:connection:sender",
76
- error: "Plan does not include a verified sending domain",
77
- feature: "Sending domain connection"
78
- },
79
74
  sendgrid: {
80
75
  key: "organization:connection:sendgrid",
81
76
  error: "Plan does not include SendGrid connection",
@@ -118,6 +113,19 @@ var organization = {
118
113
  error: "Your plan does not include brands",
119
114
  feature: "Organization brands management"
120
115
  },
116
+ // Gates the Networking section as a whole — a verified sending domain today,
117
+ // the SMS number and a custom page domain as they land. One key rather than
118
+ // one per type: they are the same capability to a merchant, and splitting
119
+ // them would mean a plan could grant half a section.
120
+ //
121
+ // It replaces `connection.sender`, which named a connection this stopped
122
+ // being. Free organizations cannot send lead-facing email at all, so a
123
+ // sending identity there is one they could never send from.
124
+ networking: {
125
+ key: "organization:networking",
126
+ error: "Your plan does not include a custom sending identity",
127
+ feature: "Organization networking"
128
+ },
121
129
  members: {
122
130
  key: "organization:members",
123
131
  error: "Your plan does not include team members",
@@ -302,7 +310,7 @@ var plans = {
302
310
  // lead-facing email at all (the send path gates on an active
303
311
  // subscription), so granting it there would offer a domain that can
304
312
  // never send from.
305
- features: all.features([connection.sender.key, organization.members.key]),
313
+ features: all.features([organization.networking.key, organization.members.key]),
306
314
  limits: all.limits({ actions: 5e3, members: 3, storage: gigabyte * 10 }),
307
315
  marketing: {
308
316
  description: "Tools to fine-tune campaigns and improve lead quality.",
@@ -323,7 +331,7 @@ var plans = {
323
331
  },
324
332
  DB00003: {
325
333
  features: all.features([
326
- connection.sender.key,
334
+ organization.networking.key,
327
335
  organization.advertisements.key,
328
336
  organization.analytics.key,
329
337
  organization.members.key,
@@ -354,7 +362,7 @@ var plans = {
354
362
  },
355
363
  DB00004: {
356
364
  features: all.features([
357
- connection.sender.key,
365
+ organization.networking.key,
358
366
  organization.advertisements.key,
359
367
  organization.analytics.key,
360
368
  organization.members.key,
@@ -385,7 +393,7 @@ var plans = {
385
393
  },
386
394
  DB00005: {
387
395
  features: all.features([
388
- connection.sender.key,
396
+ organization.networking.key,
389
397
  organization.advertisements.key,
390
398
  organization.analytics.key,
391
399
  organization.members.key,
@@ -430,7 +438,7 @@ var resolvePlan = (subscription) => {
430
438
  // baseline whether or not the deal thought to name it. Today that is the
431
439
  // sending domain: every catalog paid tier grants it, and a custom plan
432
440
  // silently lacking it would be a support ticket, not a pricing decision.
433
- features: all.features([connection.sender.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
441
+ features: all.features([organization.networking.key, ...((_a = custom.features) == null ? void 0 : _a.granted) || []]),
434
442
  limits: all.limits(((_b = custom.limits) == null ? void 0 : _b.organization) || {}),
435
443
  // A custom plan stores its overage BARE on `custom.overages` — a different
436
444
  // shape from the catalog's nested one. Number() so a deal stored as a string
@@ -725,6 +733,76 @@ var scrape = {
725
733
  };
726
734
 
727
735
  // lib/pricing.js
736
+ var emailPlans = {
737
+ essentials50k: {
738
+ included: 5e4,
739
+ // The plan's own price is not shown in the console once you are on it —
740
+ // it reads "Your Current Plan" where the price would be. Left null rather
741
+ // than guessed; fill from an invoice. Nothing derives from it yet, and an
742
+ // invented figure would quietly become the basis of an upgrade decision.
743
+ monthly: null,
744
+ overageCents: 0.133,
745
+ title: "Essentials 50K"
746
+ },
747
+ essentials100k: {
748
+ included: 1e5,
749
+ monthly: 3495,
750
+ overageCents: 0.09,
751
+ title: "Essentials 100K"
752
+ },
753
+ pro100k: {
754
+ included: 1e5,
755
+ monthly: 8995,
756
+ overageCents: 0.11,
757
+ title: "Pro 100K"
758
+ },
759
+ pro300k: {
760
+ included: 3e5,
761
+ monthly: 24900,
762
+ overageCents: 0.091,
763
+ title: "Pro 300K"
764
+ },
765
+ pro700k: {
766
+ included: 7e5,
767
+ monthly: 49900,
768
+ overageCents: 0.078,
769
+ title: "Pro 700K"
770
+ },
771
+ pro1500k: {
772
+ included: 15e5,
773
+ monthly: 79900,
774
+ overageCents: 0.059,
775
+ title: "Pro 1.5 Million"
776
+ },
777
+ pro2500k: {
778
+ included: 25e5,
779
+ monthly: 109900,
780
+ overageCents: 0.047,
781
+ title: "Pro 2.5 Million"
782
+ }
783
+ };
784
+ var emailPlan = "essentials50k";
785
+ var sending = {
786
+ email: {
787
+ // Fraction of the included volume that raises the alarm. Far enough ahead
788
+ // to act on, high enough not to fire on ordinary growth.
789
+ //
790
+ // Crossing it is NOT an outage. The provider bills overage rather than
791
+ // stopping sends, so the alarm is a COST signal: past the included volume
792
+ // every email is charged at overageCents, and at some run rate the next
793
+ // plan up is cheaper than the overage. That is the decision it exists to
794
+ // prompt, and it is why nobody should be woken by it.
795
+ alertThreshold: 0.8,
796
+ // Emails per calendar month included, PLATFORM-WIDE and across every send
797
+ // path: lead-facing mail, workflow steps, and the sign-in codes and
798
+ // account mail that no plan allowance meters.
799
+ monthlyCeiling: emailPlans[emailPlan].included,
800
+ overageCents: emailPlans[emailPlan].overageCents,
801
+ plan: emailPlan,
802
+ plans: emailPlans,
803
+ title: emailPlans[emailPlan].title
804
+ }
805
+ };
728
806
  var channels = {
729
807
  email: {
730
808
  actionsPerSend: 1,
@@ -753,5 +831,6 @@ export {
753
831
  plans,
754
832
  resolvePlan,
755
833
  scrape,
834
+ sending,
756
835
  unitAmountDecimal
757
836
  };
package/package.json CHANGED
@@ -190,5 +190,5 @@
190
190
  "test": ". \"$HOME/.nvm/nvm.sh\" && nvm use && node --test"
191
191
  },
192
192
  "types": "dist/index.d.ts",
193
- "version": "0.0.104"
193
+ "version": "0.0.106"
194
194
  }