@drawbridge/drawbridge-utils 0.0.162 → 0.0.164
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/connections/index.cjs +105 -8
- package/dist/connections/index.d.cts +104 -4
- package/dist/connections/index.d.ts +104 -4
- package/dist/connections/index.js +105 -8
- package/dist/html.cjs +5 -2
- package/dist/html.d.cts +20 -1
- package/dist/html.d.ts +20 -1
- package/dist/html.js +3 -1
- package/dist/index.cjs +34 -1
- package/dist/index.d.cts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +34 -1
- package/dist/notification.cjs +84 -20
- package/dist/notification.d.cts +117 -34
- package/dist/notification.d.ts +117 -34
- package/dist/notification.js +82 -20
- package/dist/providers.cjs +137 -17
- package/dist/providers.d.cts +104 -12
- package/dist/providers.d.ts +104 -12
- package/dist/providers.js +133 -16
- package/package.json +1 -1
|
@@ -914,10 +914,15 @@ var attentive = {
|
|
|
914
914
|
'Attentive accepts these updates and applies them in the background, so a contact appears in your segment shortly after the sync rather than the instant it runs.'
|
|
915
915
|
],
|
|
916
916
|
excerpt : 'Sync your Drawbridge contacts into an Attentive segment.',
|
|
917
|
+
// NAMES THE SEGMENT STEP, because `status` below gates on it: a grant with
|
|
918
|
+
// no segment chosen sits at Pending, and a guide that stops at the consent
|
|
919
|
+
// leaves a merchant looking at a connection they think is broken. Same
|
|
920
|
+
// omission Klaviyo's guide already carries a note about.
|
|
917
921
|
guide : [
|
|
918
922
|
'Press Connect. Drawbridge sends you to Attentive to approve access.',
|
|
919
923
|
'Sign in to Attentive if you are not already, and authorize the permissions listed.',
|
|
920
|
-
'You
|
|
924
|
+
'You come back here to choose which Attentive segment your contacts should sync into.',
|
|
925
|
+
'The connection shows Pending until you choose a segment, then Active.'
|
|
921
926
|
]
|
|
922
927
|
},
|
|
923
928
|
// A contact destination, like Klaviyo and Mailchimp — a merchant could
|
|
@@ -1239,6 +1244,10 @@ var attentive = {
|
|
|
1239
1244
|
|
|
1240
1245
|
queue : 'connection',
|
|
1241
1246
|
|
|
1247
|
+
// Attentive applies the update in the background and returns no id we
|
|
1248
|
+
// could hand on, so there is nothing for a later step to interpolate.
|
|
1249
|
+
returns : [],
|
|
1250
|
+
|
|
1242
1251
|
// Nothing for a merchant to configure on the step itself — the segment
|
|
1243
1252
|
// is chosen once on the connection. Declared empty rather than omitted,
|
|
1244
1253
|
// so "this step takes no settings" and "nobody thought about settings"
|
|
@@ -2236,6 +2245,11 @@ var drawbridge = {
|
|
|
2236
2245
|
message : interpolate( step.settings?.message, values ),
|
|
2237
2246
|
organization : workflow.organization,
|
|
2238
2247
|
send : { type : 'email', email : member.email },
|
|
2248
|
+
// The editor's plain rendering, interpolated the same way so
|
|
2249
|
+
// {{count}} resolves in the text part too. Spread rather than
|
|
2250
|
+
// written as null: a step saved before `text` existed has none,
|
|
2251
|
+
// and the send path strips the markup itself for those.
|
|
2252
|
+
...( step.settings?.text && { text : interpolate( step.settings.text, values ) }),
|
|
2239
2253
|
title : interpolate( step.settings?.subject, values ),
|
|
2240
2254
|
workflow : workflow.id
|
|
2241
2255
|
}) )
|
|
@@ -2423,6 +2437,10 @@ var drawbridge = {
|
|
|
2423
2437
|
message : interpolate( step.settings?.message, context ),
|
|
2424
2438
|
organization : workflow.organization,
|
|
2425
2439
|
send : { type : 'email', email : to },
|
|
2440
|
+
// The editor's plain rendering, so the text/plain part of this
|
|
2441
|
+
// email is what the merchant wrote rather than a regex's guess at
|
|
2442
|
+
// it. See the digest above for why it is spread, not nulled.
|
|
2443
|
+
...( step.settings?.text && { text : interpolate( step.settings.text, context ) }),
|
|
2426
2444
|
title : interpolate( step.settings?.subject, context ),
|
|
2427
2445
|
workflow : workflow.id
|
|
2428
2446
|
})
|
|
@@ -2978,6 +2996,11 @@ var drawbridge = {
|
|
|
2978
2996
|
hook : 'email.digest',
|
|
2979
2997
|
key : 'Email — Digest',
|
|
2980
2998
|
queue : 'notification',
|
|
2999
|
+
// Nothing for a later step to interpolate. Declared empty rather than
|
|
3000
|
+
// omitted, so "this step leaves nothing behind" and "nobody thought
|
|
3001
|
+
// about it" stay different statements — the second is what emptied the
|
|
3002
|
+
// builder's Variables menu for every step in the product.
|
|
3003
|
+
returns : [],
|
|
2981
3004
|
settings : {
|
|
2982
3005
|
// The organization OWNER is always a recipient, resolved by the
|
|
2983
3006
|
// hook, so this is additional recipients rather than the list. It
|
|
@@ -2986,7 +3009,22 @@ var drawbridge = {
|
|
|
2986
3009
|
// pick and could never save the step.
|
|
2987
3010
|
members : { of : 'string', type : 'array' },
|
|
2988
3011
|
message : { max : 2000, required : true, type : 'string' },
|
|
2989
|
-
|
|
3012
|
+
// THE LAST SPAM CHECK, stored beside the copy it measured. Shape is
|
|
3013
|
+
// pinned no further than the pair that proves the match, which is the
|
|
3014
|
+
// only part the api reads; the rest is SpamAssassin's own output and
|
|
3015
|
+
// is stored as it arrives. Same declaration the campaign's draw
|
|
3016
|
+
// notification validates against.
|
|
3017
|
+
//
|
|
3018
|
+
// Undeclared, this was silently dropped at submit — the settings
|
|
3019
|
+
// validator is noUnknown().strict(), so the dashboard's step form
|
|
3020
|
+
// mapped the key away rather than 400 — and every open of a workflow
|
|
3021
|
+
// step re-ran a check against a free service to be told what the last
|
|
3022
|
+
// open had already been told.
|
|
3023
|
+
score : { shape : { message : { type : 'string' }, subject : { type : 'string' } }, type : 'object' },
|
|
3024
|
+
subject : { max : 150, required : true, type : 'string' },
|
|
3025
|
+
// The editor's plain rendering of `message`, written beside it rather
|
|
3026
|
+
// than reconstructed by running a regex over the markup at send time.
|
|
3027
|
+
text : { max : 2000, type : 'string' }
|
|
2990
3028
|
},
|
|
2991
3029
|
triggers : [ 'schedule.day', 'schedule.week', 'schedule.month' ],
|
|
2992
3030
|
usage : { actions : 0 }
|
|
@@ -3008,6 +3046,7 @@ var drawbridge = {
|
|
|
3008
3046
|
hook : 'email.notify',
|
|
3009
3047
|
key : 'Email — Notification',
|
|
3010
3048
|
queue : 'notification',
|
|
3049
|
+
returns : [],
|
|
3011
3050
|
settings : {
|
|
3012
3051
|
members : { of : 'string', type : 'array' },
|
|
3013
3052
|
message : { max : 2000, type : 'string' },
|
|
@@ -3025,9 +3064,14 @@ var drawbridge = {
|
|
|
3025
3064
|
hook : 'email.send',
|
|
3026
3065
|
key : 'Email — Send email',
|
|
3027
3066
|
queue : 'notification',
|
|
3067
|
+
returns : [],
|
|
3028
3068
|
settings : {
|
|
3029
3069
|
message : { max : 2000, required : true, type : 'string' },
|
|
3030
|
-
|
|
3070
|
+
// See email.digest above — the last spam check, and the editor's
|
|
3071
|
+
// plain rendering of the message.
|
|
3072
|
+
score : { shape : { message : { type : 'string' }, subject : { type : 'string' } }, type : 'object' },
|
|
3073
|
+
subject : { max : 150, required : true, type : 'string' },
|
|
3074
|
+
text : { max : 2000, type : 'string' }
|
|
3031
3075
|
},
|
|
3032
3076
|
triggers : [ 'lead.insert' ],
|
|
3033
3077
|
// ONE SOURCE FOR THE PRICE. lib/pricing.js is the index of every
|
|
@@ -3813,6 +3857,14 @@ var klaviyo = {
|
|
|
3813
3857
|
|
|
3814
3858
|
queue : 'connection',
|
|
3815
3859
|
|
|
3860
|
+
// The key the hook puts in `context`, which the runner merges into the
|
|
3861
|
+
// run so a later step can interpolate {{klaviyoProfileId}}. Declared
|
|
3862
|
+
// beside the hook that writes it — see the note on Shopify's
|
|
3863
|
+
// commerce.code, which is where this came back from.
|
|
3864
|
+
returns : [
|
|
3865
|
+
{ key : 'klaviyoProfileId', label : 'Klaviyo Profile ID' }
|
|
3866
|
+
],
|
|
3867
|
+
|
|
3816
3868
|
// Nothing for a merchant to configure on the step itself — the list
|
|
3817
3869
|
// is chosen once on the connection. Declared empty rather than
|
|
3818
3870
|
// omitted, so "this step takes no settings" and "nobody thought about
|
|
@@ -3978,10 +4030,15 @@ var mailchimp = {
|
|
|
3978
4030
|
'Someone who unsubscribed inside Mailchimp keeps that choice: a resync only sets the status of a subscriber Mailchimp has never seen before.'
|
|
3979
4031
|
],
|
|
3980
4032
|
excerpt : 'Sync your Drawbridge contacts into a Mailchimp audience.',
|
|
4033
|
+
// NAMES THE AUDIENCE STEP, because `status` below gates on it: a grant with
|
|
4034
|
+
// no audience chosen sits at Pending, and a guide that stops at the consent
|
|
4035
|
+
// leaves a merchant looking at a connection they think is broken.
|
|
3981
4036
|
guide : [
|
|
3982
4037
|
'Press Connect. Drawbridge sends you to Mailchimp to approve access.',
|
|
3983
4038
|
'Sign in to Mailchimp if you are not already, and choose the account to connect.',
|
|
3984
|
-
'You come back here to pick the audience your contacts should sync into.'
|
|
4039
|
+
'You come back here to pick the audience your contacts should sync into.',
|
|
4040
|
+
'The connection shows Pending until you pick an audience, then Active.',
|
|
4041
|
+
'You can remove Drawbridge at any time from the Authorized Apps page in your Mailchimp account.'
|
|
3985
4042
|
]
|
|
3986
4043
|
},
|
|
3987
4044
|
// Mailchimp and SendGrid shared a group while they were SENDERS, where an org
|
|
@@ -4332,6 +4389,12 @@ var mailchimp = {
|
|
|
4332
4389
|
|
|
4333
4390
|
queue : 'connection',
|
|
4334
4391
|
|
|
4392
|
+
// The key the hook puts in `context`, for a later step to interpolate
|
|
4393
|
+
// as {{mailchimpMemberId}}. Same reasoning as Klaviyo's.
|
|
4394
|
+
returns : [
|
|
4395
|
+
{ key : 'mailchimpMemberId', label : 'Mailchimp Member ID' }
|
|
4396
|
+
],
|
|
4397
|
+
|
|
4335
4398
|
// Nothing for a merchant to configure on the step itself — the audience
|
|
4336
4399
|
// is chosen once on the connection. Declared empty rather than omitted,
|
|
4337
4400
|
// so "this step takes no settings" and "nobody thought about settings"
|
|
@@ -6096,6 +6159,24 @@ var shopify = {
|
|
|
6096
6159
|
hook : 'commerce.code',
|
|
6097
6160
|
key : 'Issue a discount code',
|
|
6098
6161
|
queue : 'connection',
|
|
6162
|
+
// WHAT THIS STEP LEAVES BEHIND FOR THE ONES AFTER IT, and the only
|
|
6163
|
+
// reason the builder can offer a Variables menu.
|
|
6164
|
+
//
|
|
6165
|
+
// These are the exact keys the hook puts in `context` — the runner
|
|
6166
|
+
// merges that into the run's context, and a later step interpolates
|
|
6167
|
+
// {{shopifyDiscountCode}} out of it. Declared beside the hook that
|
|
6168
|
+
// writes them so the two cannot drift; there is nowhere else that
|
|
6169
|
+
// knows both.
|
|
6170
|
+
//
|
|
6171
|
+
// They existed as a hand-written list in the api's workflow catalog
|
|
6172
|
+
// until it was derived from these manifests, and the derivation
|
|
6173
|
+
// hardcoded `returns : []` for every step. Nothing has offered a
|
|
6174
|
+
// variable since — an email step after a discount step had no way to
|
|
6175
|
+
// name the code it was supposed to send.
|
|
6176
|
+
returns : [
|
|
6177
|
+
{ key : 'shopifyDiscountCode', label : 'Shopify Discount Code' },
|
|
6178
|
+
{ key : 'shopifyDiscountId', label : 'Shopify Discount ID' }
|
|
6179
|
+
],
|
|
6099
6180
|
settings : {
|
|
6100
6181
|
discount : {
|
|
6101
6182
|
required : true,
|
|
@@ -6115,6 +6196,12 @@ var shopify = {
|
|
|
6115
6196
|
hook : 'commerce.customer',
|
|
6116
6197
|
key : 'Create customer',
|
|
6117
6198
|
queue : 'connection',
|
|
6199
|
+
// Written on every path the hook can take — created, reused from an
|
|
6200
|
+
// earlier step, or reused from the lead — so a later step can rely on
|
|
6201
|
+
// it being there whenever this one succeeded.
|
|
6202
|
+
returns : [
|
|
6203
|
+
{ key : 'shopifyCustomerId', label : 'Shopify Customer ID' }
|
|
6204
|
+
],
|
|
6118
6205
|
settings : {},
|
|
6119
6206
|
triggers : [ 'lead.insert' ],
|
|
6120
6207
|
usage : { actions : 1 }
|
|
@@ -6425,6 +6512,13 @@ var webhook = {
|
|
|
6425
6512
|
hook : 'webhook.send',
|
|
6426
6513
|
key : 'Send webhook',
|
|
6427
6514
|
queue : 'webhook',
|
|
6515
|
+
// The receiver's response is buffered, not merged into the run — a
|
|
6516
|
+
// receiver owes us a status, not a document — so there is nothing here
|
|
6517
|
+
// for a later step to interpolate. Declared empty rather than omitted:
|
|
6518
|
+
// "leaves nothing behind" and "nobody thought about it" are different
|
|
6519
|
+
// statements, and the second one is what emptied the builder's
|
|
6520
|
+
// Variables menu for every step in the product.
|
|
6521
|
+
returns : [],
|
|
6428
6522
|
settings : {
|
|
6429
6523
|
url : { format : 'url', required : true, type : 'string' }
|
|
6430
6524
|
},
|
|
@@ -7406,6 +7500,12 @@ const publicConnectionKeys = Object.freeze([
|
|
|
7406
7500
|
// excerpt, guide, and any vendor redirect copy.
|
|
7407
7501
|
'content',
|
|
7408
7502
|
'createdAt',
|
|
7503
|
+
// THE MERCHANT'S OWN SWITCH. `enabled : false` is a connection the merchant
|
|
7504
|
+
// paused — kept, configured, and not run — as distinct from a vendor the
|
|
7505
|
+
// platform switched off (which the api overlays as an error). Absent means
|
|
7506
|
+
// on; the dashboard renders Paused from it and the sync gates refuse a paused
|
|
7507
|
+
// connection the way they refuse a missing one.
|
|
7508
|
+
'enabled',
|
|
7409
7509
|
// The connection DOCUMENT's own errors array — scope-drift entries written by
|
|
7410
7510
|
// drawbridge-sync. NOT the manifest's error copy, which is content.errors:
|
|
7411
7511
|
// the document is spread OVER the resolved manifest downstream, so the two
|
|
@@ -558,10 +558,15 @@ var attentive_default2 = {
|
|
|
558
558
|
"Attentive accepts these updates and applies them in the background, so a contact appears in your segment shortly after the sync rather than the instant it runs."
|
|
559
559
|
],
|
|
560
560
|
excerpt: "Sync your Drawbridge contacts into an Attentive segment.",
|
|
561
|
+
// NAMES THE SEGMENT STEP, because `status` below gates on it: a grant with
|
|
562
|
+
// no segment chosen sits at Pending, and a guide that stops at the consent
|
|
563
|
+
// leaves a merchant looking at a connection they think is broken. Same
|
|
564
|
+
// omission Klaviyo's guide already carries a note about.
|
|
561
565
|
guide: [
|
|
562
566
|
"Press Connect. Drawbridge sends you to Attentive to approve access.",
|
|
563
567
|
"Sign in to Attentive if you are not already, and authorize the permissions listed.",
|
|
564
|
-
"You
|
|
568
|
+
"You come back here to choose which Attentive segment your contacts should sync into.",
|
|
569
|
+
"The connection shows Pending until you choose a segment, then Active."
|
|
565
570
|
]
|
|
566
571
|
},
|
|
567
572
|
// A contact destination, like Klaviyo and Mailchimp — a merchant could
|
|
@@ -824,6 +829,9 @@ var attentive_default2 = {
|
|
|
824
829
|
// merchant would recognise in a builder label.
|
|
825
830
|
key: "Sync contact to Attentive",
|
|
826
831
|
queue: "connection",
|
|
832
|
+
// Attentive applies the update in the background and returns no id we
|
|
833
|
+
// could hand on, so there is nothing for a later step to interpolate.
|
|
834
|
+
returns: [],
|
|
827
835
|
// Nothing for a merchant to configure on the step itself — the segment
|
|
828
836
|
// is chosen once on the connection. Declared empty rather than omitted,
|
|
829
837
|
// so "this step takes no settings" and "nobody thought about settings"
|
|
@@ -2051,13 +2059,18 @@ var drawbridge_default2 = {
|
|
|
2051
2059
|
request: request2,
|
|
2052
2060
|
response: { count, notified: recipients.length },
|
|
2053
2061
|
writes: recipients.map((member) => {
|
|
2054
|
-
var _a2, _b2;
|
|
2062
|
+
var _a2, _b2, _c2;
|
|
2055
2063
|
return queueNotification({
|
|
2056
2064
|
audience: "member",
|
|
2057
2065
|
message: interpolate((_a2 = step.settings) == null ? void 0 : _a2.message, values),
|
|
2058
2066
|
organization: workflow.organization,
|
|
2059
2067
|
send: { type: "email", email: member.email },
|
|
2060
|
-
|
|
2068
|
+
// The editor's plain rendering, interpolated the same way so
|
|
2069
|
+
// {{count}} resolves in the text part too. Spread rather than
|
|
2070
|
+
// written as null: a step saved before `text` existed has none,
|
|
2071
|
+
// and the send path strips the markup itself for those.
|
|
2072
|
+
...((_b2 = step.settings) == null ? void 0 : _b2.text) && { text: interpolate(step.settings.text, values) },
|
|
2073
|
+
title: interpolate((_c2 = step.settings) == null ? void 0 : _c2.subject, values),
|
|
2061
2074
|
workflow: workflow.id
|
|
2062
2075
|
});
|
|
2063
2076
|
})
|
|
@@ -2126,7 +2139,7 @@ var drawbridge_default2 = {
|
|
|
2126
2139
|
// unsubscribe token and the CAN-SPAM footer. The step's job is to say who
|
|
2127
2140
|
// and what, correctly, and to refuse early when it must not send at all.
|
|
2128
2141
|
send: async ({ context, step, workflow }, { canSend, read } = {}) => {
|
|
2129
|
-
var _a, _b;
|
|
2142
|
+
var _a, _b, _c;
|
|
2130
2143
|
const to = context == null ? void 0 : context.email;
|
|
2131
2144
|
if (!to) throw new Error("No email address on context (context.email is required)");
|
|
2132
2145
|
const request2 = { to };
|
|
@@ -2163,7 +2176,11 @@ var drawbridge_default2 = {
|
|
|
2163
2176
|
message: interpolate((_a = step.settings) == null ? void 0 : _a.message, context),
|
|
2164
2177
|
organization: workflow.organization,
|
|
2165
2178
|
send: { type: "email", email: to },
|
|
2166
|
-
|
|
2179
|
+
// The editor's plain rendering, so the text/plain part of this
|
|
2180
|
+
// email is what the merchant wrote rather than a regex's guess at
|
|
2181
|
+
// it. See the digest above for why it is spread, not nulled.
|
|
2182
|
+
...((_b = step.settings) == null ? void 0 : _b.text) && { text: interpolate(step.settings.text, context) },
|
|
2183
|
+
title: interpolate((_c = step.settings) == null ? void 0 : _c.subject, context),
|
|
2167
2184
|
workflow: workflow.id
|
|
2168
2185
|
})
|
|
2169
2186
|
]
|
|
@@ -2583,6 +2600,11 @@ var drawbridge_default2 = {
|
|
|
2583
2600
|
hook: "email.digest",
|
|
2584
2601
|
key: "Email \u2014 Digest",
|
|
2585
2602
|
queue: "notification",
|
|
2603
|
+
// Nothing for a later step to interpolate. Declared empty rather than
|
|
2604
|
+
// omitted, so "this step leaves nothing behind" and "nobody thought
|
|
2605
|
+
// about it" stay different statements — the second is what emptied the
|
|
2606
|
+
// builder's Variables menu for every step in the product.
|
|
2607
|
+
returns: [],
|
|
2586
2608
|
settings: {
|
|
2587
2609
|
// The organization OWNER is always a recipient, resolved by the
|
|
2588
2610
|
// hook, so this is additional recipients rather than the list. It
|
|
@@ -2591,7 +2613,22 @@ var drawbridge_default2 = {
|
|
|
2591
2613
|
// pick and could never save the step.
|
|
2592
2614
|
members: { of: "string", type: "array" },
|
|
2593
2615
|
message: { max: 2e3, required: true, type: "string" },
|
|
2594
|
-
|
|
2616
|
+
// THE LAST SPAM CHECK, stored beside the copy it measured. Shape is
|
|
2617
|
+
// pinned no further than the pair that proves the match, which is the
|
|
2618
|
+
// only part the api reads; the rest is SpamAssassin's own output and
|
|
2619
|
+
// is stored as it arrives. Same declaration the campaign's draw
|
|
2620
|
+
// notification validates against.
|
|
2621
|
+
//
|
|
2622
|
+
// Undeclared, this was silently dropped at submit — the settings
|
|
2623
|
+
// validator is noUnknown().strict(), so the dashboard's step form
|
|
2624
|
+
// mapped the key away rather than 400 — and every open of a workflow
|
|
2625
|
+
// step re-ran a check against a free service to be told what the last
|
|
2626
|
+
// open had already been told.
|
|
2627
|
+
score: { shape: { message: { type: "string" }, subject: { type: "string" } }, type: "object" },
|
|
2628
|
+
subject: { max: 150, required: true, type: "string" },
|
|
2629
|
+
// The editor's plain rendering of `message`, written beside it rather
|
|
2630
|
+
// than reconstructed by running a regex over the markup at send time.
|
|
2631
|
+
text: { max: 2e3, type: "string" }
|
|
2595
2632
|
},
|
|
2596
2633
|
triggers: ["schedule.day", "schedule.week", "schedule.month"],
|
|
2597
2634
|
usage: { actions: 0 }
|
|
@@ -2612,6 +2649,7 @@ var drawbridge_default2 = {
|
|
|
2612
2649
|
hook: "email.notify",
|
|
2613
2650
|
key: "Email \u2014 Notification",
|
|
2614
2651
|
queue: "notification",
|
|
2652
|
+
returns: [],
|
|
2615
2653
|
settings: {
|
|
2616
2654
|
members: { of: "string", type: "array" },
|
|
2617
2655
|
message: { max: 2e3, type: "string" },
|
|
@@ -2628,9 +2666,14 @@ var drawbridge_default2 = {
|
|
|
2628
2666
|
hook: "email.send",
|
|
2629
2667
|
key: "Email \u2014 Send email",
|
|
2630
2668
|
queue: "notification",
|
|
2669
|
+
returns: [],
|
|
2631
2670
|
settings: {
|
|
2632
2671
|
message: { max: 2e3, required: true, type: "string" },
|
|
2633
|
-
|
|
2672
|
+
// See email.digest above — the last spam check, and the editor's
|
|
2673
|
+
// plain rendering of the message.
|
|
2674
|
+
score: { shape: { message: { type: "string" }, subject: { type: "string" } }, type: "object" },
|
|
2675
|
+
subject: { max: 150, required: true, type: "string" },
|
|
2676
|
+
text: { max: 2e3, type: "string" }
|
|
2634
2677
|
},
|
|
2635
2678
|
triggers: ["lead.insert"],
|
|
2636
2679
|
// ONE SOURCE FOR THE PRICE. lib/pricing.js is the index of every
|
|
@@ -3229,6 +3272,13 @@ var klaviyo_default2 = {
|
|
|
3229
3272
|
// than a label that could be any of their Klaviyo accounts.
|
|
3230
3273
|
key: "Sync contact to " + (((_a = data2 == null ? void 0 : data2.settings) == null ? void 0 : _a.account) || "Klaviyo"),
|
|
3231
3274
|
queue: "connection",
|
|
3275
|
+
// The key the hook puts in `context`, which the runner merges into the
|
|
3276
|
+
// run so a later step can interpolate {{klaviyoProfileId}}. Declared
|
|
3277
|
+
// beside the hook that writes it — see the note on Shopify's
|
|
3278
|
+
// commerce.code, which is where this came back from.
|
|
3279
|
+
returns: [
|
|
3280
|
+
{ key: "klaviyoProfileId", label: "Klaviyo Profile ID" }
|
|
3281
|
+
],
|
|
3232
3282
|
// Nothing for a merchant to configure on the step itself — the list
|
|
3233
3283
|
// is chosen once on the connection. Declared empty rather than
|
|
3234
3284
|
// omitted, so "this step takes no settings" and "nobody thought about
|
|
@@ -3344,10 +3394,15 @@ var mailchimp_default2 = {
|
|
|
3344
3394
|
"Someone who unsubscribed inside Mailchimp keeps that choice: a resync only sets the status of a subscriber Mailchimp has never seen before."
|
|
3345
3395
|
],
|
|
3346
3396
|
excerpt: "Sync your Drawbridge contacts into a Mailchimp audience.",
|
|
3397
|
+
// NAMES THE AUDIENCE STEP, because `status` below gates on it: a grant with
|
|
3398
|
+
// no audience chosen sits at Pending, and a guide that stops at the consent
|
|
3399
|
+
// leaves a merchant looking at a connection they think is broken.
|
|
3347
3400
|
guide: [
|
|
3348
3401
|
"Press Connect. Drawbridge sends you to Mailchimp to approve access.",
|
|
3349
3402
|
"Sign in to Mailchimp if you are not already, and choose the account to connect.",
|
|
3350
|
-
"You come back here to pick the audience your contacts should sync into."
|
|
3403
|
+
"You come back here to pick the audience your contacts should sync into.",
|
|
3404
|
+
"The connection shows Pending until you pick an audience, then Active.",
|
|
3405
|
+
"You can remove Drawbridge at any time from the Authorized Apps page in your Mailchimp account."
|
|
3351
3406
|
]
|
|
3352
3407
|
},
|
|
3353
3408
|
// Mailchimp and SendGrid shared a group while they were SENDERS, where an org
|
|
@@ -3589,6 +3644,11 @@ var mailchimp_default2 = {
|
|
|
3589
3644
|
// than showing a string like a1b2c3d4e5.
|
|
3590
3645
|
key: "Sync contact to Mailchimp",
|
|
3591
3646
|
queue: "connection",
|
|
3647
|
+
// The key the hook puts in `context`, for a later step to interpolate
|
|
3648
|
+
// as {{mailchimpMemberId}}. Same reasoning as Klaviyo's.
|
|
3649
|
+
returns: [
|
|
3650
|
+
{ key: "mailchimpMemberId", label: "Mailchimp Member ID" }
|
|
3651
|
+
],
|
|
3592
3652
|
// Nothing for a merchant to configure on the step itself — the audience
|
|
3593
3653
|
// is chosen once on the connection. Declared empty rather than omitted,
|
|
3594
3654
|
// so "this step takes no settings" and "nobody thought about settings"
|
|
@@ -4850,6 +4910,24 @@ var shopify_default2 = {
|
|
|
4850
4910
|
hook: "commerce.code",
|
|
4851
4911
|
key: "Issue a discount code",
|
|
4852
4912
|
queue: "connection",
|
|
4913
|
+
// WHAT THIS STEP LEAVES BEHIND FOR THE ONES AFTER IT, and the only
|
|
4914
|
+
// reason the builder can offer a Variables menu.
|
|
4915
|
+
//
|
|
4916
|
+
// These are the exact keys the hook puts in `context` — the runner
|
|
4917
|
+
// merges that into the run's context, and a later step interpolates
|
|
4918
|
+
// {{shopifyDiscountCode}} out of it. Declared beside the hook that
|
|
4919
|
+
// writes them so the two cannot drift; there is nowhere else that
|
|
4920
|
+
// knows both.
|
|
4921
|
+
//
|
|
4922
|
+
// They existed as a hand-written list in the api's workflow catalog
|
|
4923
|
+
// until it was derived from these manifests, and the derivation
|
|
4924
|
+
// hardcoded `returns : []` for every step. Nothing has offered a
|
|
4925
|
+
// variable since — an email step after a discount step had no way to
|
|
4926
|
+
// name the code it was supposed to send.
|
|
4927
|
+
returns: [
|
|
4928
|
+
{ key: "shopifyDiscountCode", label: "Shopify Discount Code" },
|
|
4929
|
+
{ key: "shopifyDiscountId", label: "Shopify Discount ID" }
|
|
4930
|
+
],
|
|
4853
4931
|
settings: {
|
|
4854
4932
|
discount: {
|
|
4855
4933
|
required: true,
|
|
@@ -4868,6 +4946,12 @@ var shopify_default2 = {
|
|
|
4868
4946
|
hook: "commerce.customer",
|
|
4869
4947
|
key: "Create customer",
|
|
4870
4948
|
queue: "connection",
|
|
4949
|
+
// Written on every path the hook can take — created, reused from an
|
|
4950
|
+
// earlier step, or reused from the lead — so a later step can rely on
|
|
4951
|
+
// it being there whenever this one succeeded.
|
|
4952
|
+
returns: [
|
|
4953
|
+
{ key: "shopifyCustomerId", label: "Shopify Customer ID" }
|
|
4954
|
+
],
|
|
4871
4955
|
settings: {},
|
|
4872
4956
|
triggers: ["lead.insert"],
|
|
4873
4957
|
usage: { actions: 1 }
|
|
@@ -5265,6 +5349,13 @@ var webhook_default = {
|
|
|
5265
5349
|
hook: "webhook.send",
|
|
5266
5350
|
key: "Send webhook",
|
|
5267
5351
|
queue: "webhook",
|
|
5352
|
+
// The receiver's response is buffered, not merged into the run — a
|
|
5353
|
+
// receiver owes us a status, not a document — so there is nothing here
|
|
5354
|
+
// for a later step to interpolate. Declared empty rather than omitted:
|
|
5355
|
+
// "leaves nothing behind" and "nobody thought about it" are different
|
|
5356
|
+
// statements, and the second one is what emptied the builder's
|
|
5357
|
+
// Variables menu for every step in the product.
|
|
5358
|
+
returns: [],
|
|
5268
5359
|
settings: {
|
|
5269
5360
|
url: { format: "url", required: true, type: "string" }
|
|
5270
5361
|
},
|
|
@@ -5641,6 +5732,12 @@ var publicConnectionKeys = Object.freeze([
|
|
|
5641
5732
|
// excerpt, guide, and any vendor redirect copy.
|
|
5642
5733
|
"content",
|
|
5643
5734
|
"createdAt",
|
|
5735
|
+
// THE MERCHANT'S OWN SWITCH. `enabled : false` is a connection the merchant
|
|
5736
|
+
// paused — kept, configured, and not run — as distinct from a vendor the
|
|
5737
|
+
// platform switched off (which the api overlays as an error). Absent means
|
|
5738
|
+
// on; the dashboard renders Paused from it and the sync gates refuse a paused
|
|
5739
|
+
// connection the way they refuse a missing one.
|
|
5740
|
+
"enabled",
|
|
5644
5741
|
// The connection DOCUMENT's own errors array — scope-drift entries written by
|
|
5645
5742
|
// drawbridge-sync. NOT the manifest's error copy, which is content.errors:
|
|
5646
5743
|
// the document is spread OVER the resolved manifest downstream, so the two
|
package/dist/html.cjs
CHANGED
|
@@ -19,11 +19,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// lib/html.js
|
|
20
20
|
var html_exports = {};
|
|
21
21
|
__export(html_exports, {
|
|
22
|
-
escapeHtml: () => escapeHtml
|
|
22
|
+
escapeHtml: () => escapeHtml,
|
|
23
|
+
plainText: () => plainText
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(html_exports);
|
|
25
26
|
var escapeHtml = (value) => String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
27
|
+
var plainText = (value) => String(value ?? "").replace(/<[^>]+>/g, "").trim();
|
|
26
28
|
// Annotate the CommonJS export names for ESM import in node:
|
|
27
29
|
0 && (module.exports = {
|
|
28
|
-
escapeHtml
|
|
30
|
+
escapeHtml,
|
|
31
|
+
plainText
|
|
29
32
|
});
|
package/dist/html.d.cts
CHANGED
|
@@ -29,4 +29,23 @@ const escapeHtml = ( value ) => String( value ?? '' )
|
|
|
29
29
|
.replace( /"/g, '"' )
|
|
30
30
|
.replace( /'/g, ''' );
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
// THE OTHER DIRECTION: markup out, words kept. For the places that need a plain
|
|
33
|
+
// rendering of editor output and were not handed one — the spam check scoring a
|
|
34
|
+
// message as text/plain, and the send paths' fallback for notifications written
|
|
35
|
+
// before `text` was stored beside `message`.
|
|
36
|
+
//
|
|
37
|
+
// A REGEX, AND KNOWINGLY A GUESS. It drops tags and nothing else: entities stay
|
|
38
|
+
// as typed and block boundaries collapse to nothing, so "<p>Hi</p><p>there</p>"
|
|
39
|
+
// reads "Hithere". Tiptap's getText() is the answer where an editor is in hand,
|
|
40
|
+
// which is why the dashboard stores what the editor says rather than calling
|
|
41
|
+
// this at send time. This is for the copies that exist without it.
|
|
42
|
+
//
|
|
43
|
+
// Same three-copy story as escapeHtml above: scripts.js, the draw route and the
|
|
44
|
+
// spam check route each carried this line inline. The spam check's was missing
|
|
45
|
+
// entirely — it scored the raw markup as text/plain, so a merchant's <p> tags
|
|
46
|
+
// were measured as words while the migration scored the same copy stripped.
|
|
47
|
+
const plainText = ( value ) => String( value ?? '' )
|
|
48
|
+
.replace( /<[^>]+>/g, '' )
|
|
49
|
+
.trim();
|
|
50
|
+
|
|
51
|
+
export { escapeHtml, plainText };
|
package/dist/html.d.ts
CHANGED
|
@@ -29,4 +29,23 @@ const escapeHtml = ( value ) => String( value ?? '' )
|
|
|
29
29
|
.replace( /"/g, '"' )
|
|
30
30
|
.replace( /'/g, ''' );
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
// THE OTHER DIRECTION: markup out, words kept. For the places that need a plain
|
|
33
|
+
// rendering of editor output and were not handed one — the spam check scoring a
|
|
34
|
+
// message as text/plain, and the send paths' fallback for notifications written
|
|
35
|
+
// before `text` was stored beside `message`.
|
|
36
|
+
//
|
|
37
|
+
// A REGEX, AND KNOWINGLY A GUESS. It drops tags and nothing else: entities stay
|
|
38
|
+
// as typed and block boundaries collapse to nothing, so "<p>Hi</p><p>there</p>"
|
|
39
|
+
// reads "Hithere". Tiptap's getText() is the answer where an editor is in hand,
|
|
40
|
+
// which is why the dashboard stores what the editor says rather than calling
|
|
41
|
+
// this at send time. This is for the copies that exist without it.
|
|
42
|
+
//
|
|
43
|
+
// Same three-copy story as escapeHtml above: scripts.js, the draw route and the
|
|
44
|
+
// spam check route each carried this line inline. The spam check's was missing
|
|
45
|
+
// entirely — it scored the raw markup as text/plain, so a merchant's <p> tags
|
|
46
|
+
// were measured as words while the migration scored the same copy stripped.
|
|
47
|
+
const plainText = ( value ) => String( value ?? '' )
|
|
48
|
+
.replace( /<[^>]+>/g, '' )
|
|
49
|
+
.trim();
|
|
50
|
+
|
|
51
|
+
export { escapeHtml, plainText };
|
package/dist/html.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// lib/html.js
|
|
2
2
|
var escapeHtml = (value) => String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3
|
+
var plainText = (value) => String(value ?? "").replace(/<[^>]+>/g, "").trim();
|
|
3
4
|
export {
|
|
4
|
-
escapeHtml
|
|
5
|
+
escapeHtml,
|
|
6
|
+
plainText
|
|
5
7
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -160,7 +160,40 @@ var constants_default = {
|
|
|
160
160
|
notifications: {
|
|
161
161
|
draw: {
|
|
162
162
|
subject: "Your prize is ready to claim",
|
|
163
|
-
message: "You were selected in our giveaway. Follow the link to confirm your details and claim your prize."
|
|
163
|
+
message: "You were selected in our giveaway. Follow the link to confirm your details and claim your prize.",
|
|
164
|
+
// THE DEFAULT COPY'S SPAM SCORE, SHIPPED WITH THE COPY.
|
|
165
|
+
//
|
|
166
|
+
// A campaign is seeded from this object, so a score written here is a
|
|
167
|
+
// score every new campaign has from the moment it exists — which is
|
|
168
|
+
// what lets the dashboard's card open with an answer and ask the
|
|
169
|
+
// checker only when a merchant changes the words. Without it the copy
|
|
170
|
+
// arrived unmeasured and the panel had to go and find out, spending a
|
|
171
|
+
// request on a free service to be told what is measured right here.
|
|
172
|
+
//
|
|
173
|
+
// Verbatim what the checker returned for these two strings on
|
|
174
|
+
// 2026-09-10, so a campaign created today and one moved by
|
|
175
|
+
// backfillDrawNotifications carry the same object rather than two
|
|
176
|
+
// shapes of the same fact.
|
|
177
|
+
//
|
|
178
|
+
// The 0.5 is OURS, not the copy's: SpamAssassin charges the sending
|
|
179
|
+
// domain for its missing vowels on every message it will ever score.
|
|
180
|
+
// That is why `subtotal` — the merchant's own share — is what the
|
|
181
|
+
// dashboard reads and what it scores out of five.
|
|
182
|
+
score: {
|
|
183
|
+
rules: [
|
|
184
|
+
{ description: "From: domain has series of non-vowel letters", score: 0.5 },
|
|
185
|
+
{ description: "Informational: message was not relayed via SMTP", score: 0 },
|
|
186
|
+
{ description: "Informational: message has no Received headers", score: 0 }
|
|
187
|
+
],
|
|
188
|
+
subtotal: 0,
|
|
189
|
+
threshold: 5,
|
|
190
|
+
total: 0.5
|
|
191
|
+
},
|
|
192
|
+
// The plain rendering that rides alongside `message`. Identical to it
|
|
193
|
+
// here because the default carries no markup, and stored rather than
|
|
194
|
+
// derived because the draw notification leaves as email OR as SMS and
|
|
195
|
+
// twilio.sms drops its body in verbatim.
|
|
196
|
+
text: "You were selected in our giveaway. Follow the link to confirm your details and claim your prize."
|
|
164
197
|
}
|
|
165
198
|
},
|
|
166
199
|
settings: {
|
package/dist/index.d.cts
CHANGED
|
@@ -85,7 +85,40 @@ var constantsData = {
|
|
|
85
85
|
notifications : {
|
|
86
86
|
draw : {
|
|
87
87
|
subject : 'Your prize is ready to claim',
|
|
88
|
-
message : 'You were selected in our giveaway. Follow the link to confirm your details and claim your prize.'
|
|
88
|
+
message : 'You were selected in our giveaway. Follow the link to confirm your details and claim your prize.',
|
|
89
|
+
// THE DEFAULT COPY'S SPAM SCORE, SHIPPED WITH THE COPY.
|
|
90
|
+
//
|
|
91
|
+
// A campaign is seeded from this object, so a score written here is a
|
|
92
|
+
// score every new campaign has from the moment it exists — which is
|
|
93
|
+
// what lets the dashboard's card open with an answer and ask the
|
|
94
|
+
// checker only when a merchant changes the words. Without it the copy
|
|
95
|
+
// arrived unmeasured and the panel had to go and find out, spending a
|
|
96
|
+
// request on a free service to be told what is measured right here.
|
|
97
|
+
//
|
|
98
|
+
// Verbatim what the checker returned for these two strings on
|
|
99
|
+
// 2026-09-10, so a campaign created today and one moved by
|
|
100
|
+
// backfillDrawNotifications carry the same object rather than two
|
|
101
|
+
// shapes of the same fact.
|
|
102
|
+
//
|
|
103
|
+
// The 0.5 is OURS, not the copy's: SpamAssassin charges the sending
|
|
104
|
+
// domain for its missing vowels on every message it will ever score.
|
|
105
|
+
// That is why `subtotal` — the merchant's own share — is what the
|
|
106
|
+
// dashboard reads and what it scores out of five.
|
|
107
|
+
score : {
|
|
108
|
+
rules : [
|
|
109
|
+
{ description : 'From: domain has series of non-vowel letters', score : 0.5 },
|
|
110
|
+
{ description : 'Informational: message was not relayed via SMTP', score : 0 },
|
|
111
|
+
{ description : 'Informational: message has no Received headers', score : 0 }
|
|
112
|
+
],
|
|
113
|
+
subtotal : 0,
|
|
114
|
+
threshold : 5,
|
|
115
|
+
total : 0.5
|
|
116
|
+
},
|
|
117
|
+
// The plain rendering that rides alongside `message`. Identical to it
|
|
118
|
+
// here because the default carries no markup, and stored rather than
|
|
119
|
+
// derived because the draw notification leaves as email OR as SMS and
|
|
120
|
+
// twilio.sms drops its body in verbatim.
|
|
121
|
+
text : 'You were selected in our giveaway. Follow the link to confirm your details and claim your prize.'
|
|
89
122
|
}
|
|
90
123
|
},
|
|
91
124
|
settings : {
|