@drawbridge/drawbridge-utils 0.0.161 → 0.0.163
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 +99 -8
- package/dist/connections/index.d.cts +98 -4
- package/dist/connections/index.d.ts +98 -4
- package/dist/connections/index.js +99 -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 +4 -8
- package/dist/notification.d.cts +23 -16
- package/dist/notification.d.ts +23 -16
- package/dist/notification.js +4 -8
- package/dist/providers.cjs +131 -17
- package/dist/providers.d.cts +104 -12
- package/dist/providers.d.ts +104 -12
- package/dist/providers.js +127 -16
- package/package.json +1 -1
|
@@ -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
|
},
|
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 : {
|
package/dist/index.d.ts
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 : {
|
package/dist/index.js
CHANGED
|
@@ -105,7 +105,40 @@ var constants_default = {
|
|
|
105
105
|
notifications: {
|
|
106
106
|
draw: {
|
|
107
107
|
subject: "Your prize is ready to claim",
|
|
108
|
-
message: "You were selected in our giveaway. Follow the link to confirm your details and claim your prize."
|
|
108
|
+
message: "You were selected in our giveaway. Follow the link to confirm your details and claim your prize.",
|
|
109
|
+
// THE DEFAULT COPY'S SPAM SCORE, SHIPPED WITH THE COPY.
|
|
110
|
+
//
|
|
111
|
+
// A campaign is seeded from this object, so a score written here is a
|
|
112
|
+
// score every new campaign has from the moment it exists — which is
|
|
113
|
+
// what lets the dashboard's card open with an answer and ask the
|
|
114
|
+
// checker only when a merchant changes the words. Without it the copy
|
|
115
|
+
// arrived unmeasured and the panel had to go and find out, spending a
|
|
116
|
+
// request on a free service to be told what is measured right here.
|
|
117
|
+
//
|
|
118
|
+
// Verbatim what the checker returned for these two strings on
|
|
119
|
+
// 2026-09-10, so a campaign created today and one moved by
|
|
120
|
+
// backfillDrawNotifications carry the same object rather than two
|
|
121
|
+
// shapes of the same fact.
|
|
122
|
+
//
|
|
123
|
+
// The 0.5 is OURS, not the copy's: SpamAssassin charges the sending
|
|
124
|
+
// domain for its missing vowels on every message it will ever score.
|
|
125
|
+
// That is why `subtotal` — the merchant's own share — is what the
|
|
126
|
+
// dashboard reads and what it scores out of five.
|
|
127
|
+
score: {
|
|
128
|
+
rules: [
|
|
129
|
+
{ description: "From: domain has series of non-vowel letters", score: 0.5 },
|
|
130
|
+
{ description: "Informational: message was not relayed via SMTP", score: 0 },
|
|
131
|
+
{ description: "Informational: message has no Received headers", score: 0 }
|
|
132
|
+
],
|
|
133
|
+
subtotal: 0,
|
|
134
|
+
threshold: 5,
|
|
135
|
+
total: 0.5
|
|
136
|
+
},
|
|
137
|
+
// The plain rendering that rides alongside `message`. Identical to it
|
|
138
|
+
// here because the default carries no markup, and stored rather than
|
|
139
|
+
// derived because the draw notification leaves as email OR as SMS and
|
|
140
|
+
// twilio.sms drops its body in verbatim.
|
|
141
|
+
text: "You were selected in our giveaway. Follow the link to confirm your details and claim your prize."
|
|
109
142
|
}
|
|
110
143
|
},
|
|
111
144
|
settings: {
|
package/dist/notification.cjs
CHANGED
|
@@ -77,16 +77,12 @@ var messageWarnings = ({ message }) => {
|
|
|
77
77
|
};
|
|
78
78
|
var notificationWarnings = ({
|
|
79
79
|
brand,
|
|
80
|
-
channel = "email",
|
|
81
80
|
message,
|
|
82
81
|
subject
|
|
83
|
-
} = {}) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
subject: subjectWarnings({ brand, subject })
|
|
88
|
-
};
|
|
89
|
-
};
|
|
82
|
+
} = {}) => ({
|
|
83
|
+
message: messageWarnings({ message }),
|
|
84
|
+
subject: subjectWarnings({ brand, subject })
|
|
85
|
+
});
|
|
90
86
|
// Annotate the CommonJS export names for ESM import in node:
|
|
91
87
|
0 && (module.exports = {
|
|
92
88
|
notificationWarnings
|
package/dist/notification.d.cts
CHANGED
|
@@ -131,25 +131,32 @@ const messageWarnings = ({ message }) => {
|
|
|
131
131
|
// ONE ENTRY POINT, answering per field so each warning renders against the
|
|
132
132
|
// input it is about rather than as a pile at the bottom of the form.
|
|
133
133
|
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
134
|
+
// NO CHANNEL ARGUMENT, deliberately, and it is worth saying why since one was
|
|
135
|
+
// tried twice.
|
|
136
|
+
//
|
|
137
|
+
// First it gated SMS to silence, on the grounds that carriers filter on things
|
|
138
|
+
// these rules do not look at. That much is true, and it does not follow:
|
|
139
|
+
// shouting, repeated punctuation, currency amounts and scam-shaped phrasing
|
|
140
|
+
// matter at least as much on a phone, and an unbranded text is the exact shape
|
|
141
|
+
// of a smishing attempt. Then a hedge value was added for copy that could go
|
|
142
|
+
// either way, a caller passed it to a build that did not have it, and a whole
|
|
143
|
+
// form went quiet with nothing to show for the caution.
|
|
144
|
+
//
|
|
145
|
+
// Every rule here is one a person would want to hear wherever the words end up.
|
|
146
|
+
// Sorting them by channel bought accuracy on a single warning — the subject
|
|
147
|
+
// display limit, which is really an email idea — and cost the other six.
|
|
148
|
+
//
|
|
149
|
+
// SMS still has no rules of its OWN: SHAFT categories, link shorteners, opt-out
|
|
150
|
+
// wording, and the segment mechanics where one emoji drops the limit from 160
|
|
151
|
+
// characters to 70. Those belong with the SMS connection work rather than here,
|
|
152
|
+
// and their absence is a reason to write them, not to stay quiet meanwhile.
|
|
139
153
|
const notificationWarnings = ({
|
|
140
154
|
brand,
|
|
141
|
-
channel = 'email',
|
|
142
155
|
message,
|
|
143
156
|
subject
|
|
144
|
-
} = {}) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
message : messageWarnings({ message }),
|
|
150
|
-
subject : subjectWarnings({ brand, subject })
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
};
|
|
157
|
+
} = {}) => ({
|
|
158
|
+
message : messageWarnings({ message }),
|
|
159
|
+
subject : subjectWarnings({ brand, subject })
|
|
160
|
+
});
|
|
154
161
|
|
|
155
162
|
export { notificationWarnings };
|
package/dist/notification.d.ts
CHANGED
|
@@ -131,25 +131,32 @@ const messageWarnings = ({ message }) => {
|
|
|
131
131
|
// ONE ENTRY POINT, answering per field so each warning renders against the
|
|
132
132
|
// input it is about rather than as a pile at the bottom of the form.
|
|
133
133
|
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
134
|
+
// NO CHANNEL ARGUMENT, deliberately, and it is worth saying why since one was
|
|
135
|
+
// tried twice.
|
|
136
|
+
//
|
|
137
|
+
// First it gated SMS to silence, on the grounds that carriers filter on things
|
|
138
|
+
// these rules do not look at. That much is true, and it does not follow:
|
|
139
|
+
// shouting, repeated punctuation, currency amounts and scam-shaped phrasing
|
|
140
|
+
// matter at least as much on a phone, and an unbranded text is the exact shape
|
|
141
|
+
// of a smishing attempt. Then a hedge value was added for copy that could go
|
|
142
|
+
// either way, a caller passed it to a build that did not have it, and a whole
|
|
143
|
+
// form went quiet with nothing to show for the caution.
|
|
144
|
+
//
|
|
145
|
+
// Every rule here is one a person would want to hear wherever the words end up.
|
|
146
|
+
// Sorting them by channel bought accuracy on a single warning — the subject
|
|
147
|
+
// display limit, which is really an email idea — and cost the other six.
|
|
148
|
+
//
|
|
149
|
+
// SMS still has no rules of its OWN: SHAFT categories, link shorteners, opt-out
|
|
150
|
+
// wording, and the segment mechanics where one emoji drops the limit from 160
|
|
151
|
+
// characters to 70. Those belong with the SMS connection work rather than here,
|
|
152
|
+
// and their absence is a reason to write them, not to stay quiet meanwhile.
|
|
139
153
|
const notificationWarnings = ({
|
|
140
154
|
brand,
|
|
141
|
-
channel = 'email',
|
|
142
155
|
message,
|
|
143
156
|
subject
|
|
144
|
-
} = {}) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
message : messageWarnings({ message }),
|
|
150
|
-
subject : subjectWarnings({ brand, subject })
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
};
|
|
157
|
+
} = {}) => ({
|
|
158
|
+
message : messageWarnings({ message }),
|
|
159
|
+
subject : subjectWarnings({ brand, subject })
|
|
160
|
+
});
|
|
154
161
|
|
|
155
162
|
export { notificationWarnings };
|
package/dist/notification.js
CHANGED
|
@@ -54,16 +54,12 @@ var messageWarnings = ({ message }) => {
|
|
|
54
54
|
};
|
|
55
55
|
var notificationWarnings = ({
|
|
56
56
|
brand,
|
|
57
|
-
channel = "email",
|
|
58
57
|
message,
|
|
59
58
|
subject
|
|
60
|
-
} = {}) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
subject: subjectWarnings({ brand, subject })
|
|
65
|
-
};
|
|
66
|
-
};
|
|
59
|
+
} = {}) => ({
|
|
60
|
+
message: messageWarnings({ message }),
|
|
61
|
+
subject: subjectWarnings({ brand, subject })
|
|
62
|
+
});
|
|
67
63
|
export {
|
|
68
64
|
notificationWarnings
|
|
69
65
|
};
|