@drawbridge/drawbridge-utils 0.0.164 → 0.0.165
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/notification.cjs +2 -9
- package/dist/notification.d.cts +9 -16
- package/dist/notification.d.ts +9 -16
- package/dist/notification.js +2 -9
- package/dist/sendgrid.cjs +45 -0
- package/dist/sendgrid.d.cts +72 -1
- package/dist/sendgrid.d.ts +72 -1
- package/dist/sendgrid.js +43 -0
- package/dist/twilio.cjs +8 -0
- package/dist/twilio.d.cts +18 -1
- package/dist/twilio.d.ts +18 -1
- package/dist/twilio.js +8 -0
- package/package.json +1 -1
package/dist/notification.cjs
CHANGED
|
@@ -52,7 +52,6 @@ var phrasesIn = (value) => {
|
|
|
52
52
|
};
|
|
53
53
|
var list = (values) => values.map((value) => "\u201C" + value + "\u201D").join(", ");
|
|
54
54
|
var POINTS = {
|
|
55
|
-
brand: 0,
|
|
56
55
|
currency: 1,
|
|
57
56
|
emoji: 0.5,
|
|
58
57
|
length: 0.5,
|
|
@@ -60,7 +59,7 @@ var POINTS = {
|
|
|
60
59
|
punctuation: 1,
|
|
61
60
|
shouting: 1.5
|
|
62
61
|
};
|
|
63
|
-
var subjectRules = ({
|
|
62
|
+
var subjectRules = ({ subject }) => {
|
|
64
63
|
const value = String(subject || "").trim();
|
|
65
64
|
if (!value) return [];
|
|
66
65
|
const found = phrasesIn(value);
|
|
@@ -95,11 +94,6 @@ var subjectRules = ({ brand, subject }) => {
|
|
|
95
94
|
message: "Reads like a scam to filters and to people: " + list(found) + ".",
|
|
96
95
|
points: POINTS.phrase * found.length,
|
|
97
96
|
rule: "phrase"
|
|
98
|
-
},
|
|
99
|
-
brand && !value.toLowerCase().includes(String(brand).toLowerCase()) && {
|
|
100
|
-
message: "Nothing here says the mail is from " + brand + " \u2014 subjects that name the sender get opened more.",
|
|
101
|
-
points: POINTS.brand,
|
|
102
|
-
rule: "brand"
|
|
103
97
|
}
|
|
104
98
|
].filter(Boolean).map((rule) => ({ ...rule, field: "subject" }));
|
|
105
99
|
};
|
|
@@ -131,11 +125,10 @@ var messageRules = ({ message }) => {
|
|
|
131
125
|
].filter(Boolean).map((rule) => ({ ...rule, field: "message" }));
|
|
132
126
|
};
|
|
133
127
|
var notificationRules = ({
|
|
134
|
-
brand,
|
|
135
128
|
message,
|
|
136
129
|
subject
|
|
137
130
|
} = {}) => [
|
|
138
|
-
...subjectRules({
|
|
131
|
+
...subjectRules({ subject }),
|
|
139
132
|
...messageRules({ message })
|
|
140
133
|
];
|
|
141
134
|
var notificationWarnings = (input) => {
|
package/dist/notification.d.cts
CHANGED
|
@@ -89,15 +89,18 @@ const list = ( values ) => values.map( ( value ) => '“' + value + '”' ).join
|
|
|
89
89
|
// Weighted by how reliably each one gets mail binned, and by how likely it is
|
|
90
90
|
// to be an accident. Shouting and scam phrasing are never accidents. Repeated
|
|
91
91
|
// punctuation and a currency amount are strong. Emoji and length are habits
|
|
92
|
-
// worth a nudge.
|
|
93
|
-
//
|
|
94
|
-
//
|
|
92
|
+
// worth a nudge.
|
|
93
|
+
//
|
|
94
|
+
// NO BRAND RULE. There was one — "nothing here says the mail is from Acme" —
|
|
95
|
+
// and it was asking the merchant to do by hand what the send already does:
|
|
96
|
+
// email goes out with the organization's title as the From name, and SMS puts
|
|
97
|
+
// it on the first line of the body (twilio.sms). Advice to repeat what the
|
|
98
|
+
// envelope carries is noise, and a static default could never satisfy it.
|
|
95
99
|
//
|
|
96
100
|
// PHRASES COUNT PER MATCH. "Congratulations, click here, limited time" is
|
|
97
101
|
// three of them and reads like three of them; one sentence lists them all,
|
|
98
102
|
// the points say how many. A product table, and Darren's to move.
|
|
99
103
|
const POINTS = {
|
|
100
|
-
brand : 0,
|
|
101
104
|
currency : 1,
|
|
102
105
|
emoji : 0.5,
|
|
103
106
|
length : 0.5,
|
|
@@ -106,11 +109,7 @@ const POINTS = {
|
|
|
106
109
|
shouting : 1.5
|
|
107
110
|
};
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
// writing is the single most recognisable trait of the mail people delete
|
|
111
|
-
// unread — and the one a merchant can fix in five seconds once it is pointed
|
|
112
|
-
// out. Skipped entirely when no brand is supplied rather than guessed at.
|
|
113
|
-
const subjectRules = ({ brand, subject }) => {
|
|
112
|
+
const subjectRules = ({ subject }) => {
|
|
114
113
|
|
|
115
114
|
const value = String( subject || '' ).trim();
|
|
116
115
|
|
|
@@ -149,11 +148,6 @@ const subjectRules = ({ brand, subject }) => {
|
|
|
149
148
|
message : 'Reads like a scam to filters and to people: ' + list( found ) + '.',
|
|
150
149
|
points : POINTS.phrase * found.length,
|
|
151
150
|
rule : 'phrase'
|
|
152
|
-
},
|
|
153
|
-
brand && ! value.toLowerCase().includes( String( brand ).toLowerCase() ) && {
|
|
154
|
-
message : 'Nothing here says the mail is from ' + brand + ' — subjects that name the sender get opened more.',
|
|
155
|
-
points : POINTS.brand,
|
|
156
|
-
rule : 'brand'
|
|
157
151
|
}
|
|
158
152
|
].filter( Boolean ).map( ( rule ) => ({ ...rule, field : 'subject' }) );
|
|
159
153
|
|
|
@@ -221,11 +215,10 @@ const messageRules = ({ message }) => {
|
|
|
221
215
|
// characters to 70. Those belong with the SMS connection work rather than here,
|
|
222
216
|
// and their absence is a reason to write them, not to stay quiet meanwhile.
|
|
223
217
|
const notificationRules = ({
|
|
224
|
-
brand,
|
|
225
218
|
message,
|
|
226
219
|
subject
|
|
227
220
|
} = {}) => [
|
|
228
|
-
...subjectRules({
|
|
221
|
+
...subjectRules({ subject }),
|
|
229
222
|
...messageRules({ message })
|
|
230
223
|
];
|
|
231
224
|
|
package/dist/notification.d.ts
CHANGED
|
@@ -89,15 +89,18 @@ const list = ( values ) => values.map( ( value ) => '“' + value + '”' ).join
|
|
|
89
89
|
// Weighted by how reliably each one gets mail binned, and by how likely it is
|
|
90
90
|
// to be an accident. Shouting and scam phrasing are never accidents. Repeated
|
|
91
91
|
// punctuation and a currency amount are strong. Emoji and length are habits
|
|
92
|
-
// worth a nudge.
|
|
93
|
-
//
|
|
94
|
-
//
|
|
92
|
+
// worth a nudge.
|
|
93
|
+
//
|
|
94
|
+
// NO BRAND RULE. There was one — "nothing here says the mail is from Acme" —
|
|
95
|
+
// and it was asking the merchant to do by hand what the send already does:
|
|
96
|
+
// email goes out with the organization's title as the From name, and SMS puts
|
|
97
|
+
// it on the first line of the body (twilio.sms). Advice to repeat what the
|
|
98
|
+
// envelope carries is noise, and a static default could never satisfy it.
|
|
95
99
|
//
|
|
96
100
|
// PHRASES COUNT PER MATCH. "Congratulations, click here, limited time" is
|
|
97
101
|
// three of them and reads like three of them; one sentence lists them all,
|
|
98
102
|
// the points say how many. A product table, and Darren's to move.
|
|
99
103
|
const POINTS = {
|
|
100
|
-
brand : 0,
|
|
101
104
|
currency : 1,
|
|
102
105
|
emoji : 0.5,
|
|
103
106
|
length : 0.5,
|
|
@@ -106,11 +109,7 @@ const POINTS = {
|
|
|
106
109
|
shouting : 1.5
|
|
107
110
|
};
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
// writing is the single most recognisable trait of the mail people delete
|
|
111
|
-
// unread — and the one a merchant can fix in five seconds once it is pointed
|
|
112
|
-
// out. Skipped entirely when no brand is supplied rather than guessed at.
|
|
113
|
-
const subjectRules = ({ brand, subject }) => {
|
|
112
|
+
const subjectRules = ({ subject }) => {
|
|
114
113
|
|
|
115
114
|
const value = String( subject || '' ).trim();
|
|
116
115
|
|
|
@@ -149,11 +148,6 @@ const subjectRules = ({ brand, subject }) => {
|
|
|
149
148
|
message : 'Reads like a scam to filters and to people: ' + list( found ) + '.',
|
|
150
149
|
points : POINTS.phrase * found.length,
|
|
151
150
|
rule : 'phrase'
|
|
152
|
-
},
|
|
153
|
-
brand && ! value.toLowerCase().includes( String( brand ).toLowerCase() ) && {
|
|
154
|
-
message : 'Nothing here says the mail is from ' + brand + ' — subjects that name the sender get opened more.',
|
|
155
|
-
points : POINTS.brand,
|
|
156
|
-
rule : 'brand'
|
|
157
151
|
}
|
|
158
152
|
].filter( Boolean ).map( ( rule ) => ({ ...rule, field : 'subject' }) );
|
|
159
153
|
|
|
@@ -221,11 +215,10 @@ const messageRules = ({ message }) => {
|
|
|
221
215
|
// characters to 70. Those belong with the SMS connection work rather than here,
|
|
222
216
|
// and their absence is a reason to write them, not to stay quiet meanwhile.
|
|
223
217
|
const notificationRules = ({
|
|
224
|
-
brand,
|
|
225
218
|
message,
|
|
226
219
|
subject
|
|
227
220
|
} = {}) => [
|
|
228
|
-
...subjectRules({
|
|
221
|
+
...subjectRules({ subject }),
|
|
229
222
|
...messageRules({ message })
|
|
230
223
|
];
|
|
231
224
|
|
package/dist/notification.js
CHANGED
|
@@ -27,7 +27,6 @@ var phrasesIn = (value) => {
|
|
|
27
27
|
};
|
|
28
28
|
var list = (values) => values.map((value) => "\u201C" + value + "\u201D").join(", ");
|
|
29
29
|
var POINTS = {
|
|
30
|
-
brand: 0,
|
|
31
30
|
currency: 1,
|
|
32
31
|
emoji: 0.5,
|
|
33
32
|
length: 0.5,
|
|
@@ -35,7 +34,7 @@ var POINTS = {
|
|
|
35
34
|
punctuation: 1,
|
|
36
35
|
shouting: 1.5
|
|
37
36
|
};
|
|
38
|
-
var subjectRules = ({
|
|
37
|
+
var subjectRules = ({ subject }) => {
|
|
39
38
|
const value = String(subject || "").trim();
|
|
40
39
|
if (!value) return [];
|
|
41
40
|
const found = phrasesIn(value);
|
|
@@ -70,11 +69,6 @@ var subjectRules = ({ brand, subject }) => {
|
|
|
70
69
|
message: "Reads like a scam to filters and to people: " + list(found) + ".",
|
|
71
70
|
points: POINTS.phrase * found.length,
|
|
72
71
|
rule: "phrase"
|
|
73
|
-
},
|
|
74
|
-
brand && !value.toLowerCase().includes(String(brand).toLowerCase()) && {
|
|
75
|
-
message: "Nothing here says the mail is from " + brand + " \u2014 subjects that name the sender get opened more.",
|
|
76
|
-
points: POINTS.brand,
|
|
77
|
-
rule: "brand"
|
|
78
72
|
}
|
|
79
73
|
].filter(Boolean).map((rule) => ({ ...rule, field: "subject" }));
|
|
80
74
|
};
|
|
@@ -106,11 +100,10 @@ var messageRules = ({ message }) => {
|
|
|
106
100
|
].filter(Boolean).map((rule) => ({ ...rule, field: "message" }));
|
|
107
101
|
};
|
|
108
102
|
var notificationRules = ({
|
|
109
|
-
brand,
|
|
110
103
|
message,
|
|
111
104
|
subject
|
|
112
105
|
} = {}) => [
|
|
113
|
-
...subjectRules({
|
|
106
|
+
...subjectRules({ subject }),
|
|
114
107
|
...messageRules({ message })
|
|
115
108
|
];
|
|
116
109
|
var notificationWarnings = (input) => {
|
package/dist/sendgrid.cjs
CHANGED
|
@@ -19,6 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// lib/sendgrid.js
|
|
20
20
|
var sendgrid_exports = {};
|
|
21
21
|
__export(sendgrid_exports, {
|
|
22
|
+
dnsRows: () => dnsRows,
|
|
23
|
+
domain: () => domain,
|
|
22
24
|
sendWithRetry: () => sendWithRetry,
|
|
23
25
|
sendgrid: () => sendgrid,
|
|
24
26
|
sendgridRequest: () => sendgridRequest
|
|
@@ -111,7 +113,48 @@ var sendgridRequest = ({ apiKey, body, method, path, query, request: request2 =
|
|
|
111
113
|
url: SENDGRID_BASE + path
|
|
112
114
|
});
|
|
113
115
|
};
|
|
116
|
+
var dnsRows = (dns) => Object.values(dns || {}).filter((row) => (row == null ? void 0 : row.host) && (row == null ? void 0 : row.type)).map((row) => ({
|
|
117
|
+
data: String(row.data ?? ""),
|
|
118
|
+
host: String(row.host),
|
|
119
|
+
type: String(row.type),
|
|
120
|
+
valid: typeof row.valid === "boolean" ? row.valid : null
|
|
121
|
+
}));
|
|
122
|
+
var domain = {
|
|
123
|
+
// automatic_security is what makes this CNAME-based: the provider holds the
|
|
124
|
+
// keys and the merchant publishes three CNAMEs. Without it they paste DKIM
|
|
125
|
+
// keys into TXT records by hand.
|
|
126
|
+
register: async ({ apiKey, domain: name, via = sendgridRequest }) => {
|
|
127
|
+
const created = await via({
|
|
128
|
+
apiKey,
|
|
129
|
+
body: {
|
|
130
|
+
automatic_security: true,
|
|
131
|
+
domain: name
|
|
132
|
+
},
|
|
133
|
+
method: "POST",
|
|
134
|
+
path: "/v3/whitelabel/domains"
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
records: dnsRows(created == null ? void 0 : created.dns),
|
|
138
|
+
reference: (created == null ? void 0 : created.id) == null ? null : String(created.id)
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
// Asks SendGrid to look the records up now. `records` carries per-record
|
|
142
|
+
// validity so a UI can say WHICH record has not landed rather than only that
|
|
143
|
+
// something has not; `valid` is the whole domain's verdict.
|
|
144
|
+
validate: async ({ apiKey, reference, via = sendgridRequest }) => {
|
|
145
|
+
const result = await via({
|
|
146
|
+
apiKey,
|
|
147
|
+
method: "POST",
|
|
148
|
+
path: "/v3/whitelabel/domains/" + encodeURIComponent(reference) + "/validate"
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
records: dnsRows(result == null ? void 0 : result.validation_results),
|
|
152
|
+
valid: Boolean(result == null ? void 0 : result.valid)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
};
|
|
114
156
|
var sendgrid = {
|
|
157
|
+
domain,
|
|
115
158
|
// `headers` (optional) carries the List-Unsubscribe pair on lead-facing
|
|
116
159
|
// commercial sends; omitted, the request body is byte-identical to the
|
|
117
160
|
// pre-opt-out-floor shape so system mail is untouched.
|
|
@@ -177,6 +220,8 @@ var sendgrid = {
|
|
|
177
220
|
};
|
|
178
221
|
// Annotate the CommonJS export names for ESM import in node:
|
|
179
222
|
0 && (module.exports = {
|
|
223
|
+
dnsRows,
|
|
224
|
+
domain,
|
|
180
225
|
sendWithRetry,
|
|
181
226
|
sendgrid,
|
|
182
227
|
sendgridRequest
|
package/dist/sendgrid.d.cts
CHANGED
|
@@ -109,8 +109,79 @@ const sendgridRequest = ({ apiKey, body, method, path, query, request: request$1
|
|
|
109
109
|
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
+
// SendGrid returns the records to publish as an OBJECT keyed by their role
|
|
113
|
+
// (mail_cname, dkim1, dkim2), not a list. The keys are provider vocabulary the
|
|
114
|
+
// merchant never sees and the set differs by security mode, so they are
|
|
115
|
+
// flattened to the rows the identity schema stores — the UI then renders
|
|
116
|
+
// whatever came back instead of naming roles it would have to keep in step
|
|
117
|
+
// with.
|
|
118
|
+
const dnsRows = ( dns ) => Object.values( dns || {} )
|
|
119
|
+
.filter( ( row ) => row?.host && row?.type )
|
|
120
|
+
.map( ( row ) => ({
|
|
121
|
+
data : String( row.data ?? '' ),
|
|
122
|
+
host : String( row.host ),
|
|
123
|
+
type : String( row.type ),
|
|
124
|
+
valid : typeof row.valid === 'boolean' ? row.valid : null
|
|
125
|
+
}) );
|
|
126
|
+
|
|
127
|
+
// DOMAIN AUTHENTICATION, the two calls behind a custom sending domain. They
|
|
128
|
+
// lived inline in sync's identity worker; the api needed the second one too, so
|
|
129
|
+
// a merchant pressing Check now gets an answer in the request rather than a
|
|
130
|
+
// timestamp that a change stream turns into a job that answers a socket later.
|
|
131
|
+
// One copy, both callers, same shape back.
|
|
132
|
+
//
|
|
133
|
+
// `via` is the SendGrid-level call, injectable: sync's identity worker stubs
|
|
134
|
+
// exactly that boundary (its tests assert on the `path`), and tsup's exports
|
|
135
|
+
// are read-only so a test cannot reassign one.
|
|
136
|
+
const domain = {
|
|
137
|
+
|
|
138
|
+
// automatic_security is what makes this CNAME-based: the provider holds the
|
|
139
|
+
// keys and the merchant publishes three CNAMEs. Without it they paste DKIM
|
|
140
|
+
// keys into TXT records by hand.
|
|
141
|
+
register : async ({ apiKey, domain : name, via = sendgridRequest }) => {
|
|
142
|
+
|
|
143
|
+
const created = await via({
|
|
144
|
+
apiKey,
|
|
145
|
+
body : {
|
|
146
|
+
automatic_security : true,
|
|
147
|
+
domain : name
|
|
148
|
+
},
|
|
149
|
+
method : 'POST',
|
|
150
|
+
path : '/v3/whitelabel/domains'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
records : dnsRows( created?.dns ),
|
|
155
|
+
reference : created?.id == null ? null : String( created.id )
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Asks SendGrid to look the records up now. `records` carries per-record
|
|
161
|
+
// validity so a UI can say WHICH record has not landed rather than only that
|
|
162
|
+
// something has not; `valid` is the whole domain's verdict.
|
|
163
|
+
validate : async ({ apiKey, reference, via = sendgridRequest }) => {
|
|
164
|
+
|
|
165
|
+
const result = await via({
|
|
166
|
+
apiKey,
|
|
167
|
+
method : 'POST',
|
|
168
|
+
path : '/v3/whitelabel/domains/' + encodeURIComponent( reference ) + '/validate'
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
records : dnsRows( result?.validation_results ),
|
|
173
|
+
valid : Boolean( result?.valid )
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
};
|
|
179
|
+
|
|
112
180
|
const sendgrid = {
|
|
113
181
|
|
|
182
|
+
domain,
|
|
183
|
+
|
|
184
|
+
|
|
114
185
|
// `headers` (optional) carries the List-Unsubscribe pair on lead-facing
|
|
115
186
|
// commercial sends; omitted, the request body is byte-identical to the
|
|
116
187
|
// pre-opt-out-floor shape so system mail is untouched.
|
|
@@ -210,4 +281,4 @@ const sendgrid = {
|
|
|
210
281
|
// domain-authentication endpoints (/v3/whitelabel/domains) through the same
|
|
211
282
|
// authenticated client the mail send uses, rather than minting a second one.
|
|
212
283
|
|
|
213
|
-
export { sendWithRetry, sendgrid, sendgridRequest };
|
|
284
|
+
export { dnsRows, domain, sendWithRetry, sendgrid, sendgridRequest };
|
package/dist/sendgrid.d.ts
CHANGED
|
@@ -109,8 +109,79 @@ const sendgridRequest = ({ apiKey, body, method, path, query, request: request$1
|
|
|
109
109
|
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
+
// SendGrid returns the records to publish as an OBJECT keyed by their role
|
|
113
|
+
// (mail_cname, dkim1, dkim2), not a list. The keys are provider vocabulary the
|
|
114
|
+
// merchant never sees and the set differs by security mode, so they are
|
|
115
|
+
// flattened to the rows the identity schema stores — the UI then renders
|
|
116
|
+
// whatever came back instead of naming roles it would have to keep in step
|
|
117
|
+
// with.
|
|
118
|
+
const dnsRows = ( dns ) => Object.values( dns || {} )
|
|
119
|
+
.filter( ( row ) => row?.host && row?.type )
|
|
120
|
+
.map( ( row ) => ({
|
|
121
|
+
data : String( row.data ?? '' ),
|
|
122
|
+
host : String( row.host ),
|
|
123
|
+
type : String( row.type ),
|
|
124
|
+
valid : typeof row.valid === 'boolean' ? row.valid : null
|
|
125
|
+
}) );
|
|
126
|
+
|
|
127
|
+
// DOMAIN AUTHENTICATION, the two calls behind a custom sending domain. They
|
|
128
|
+
// lived inline in sync's identity worker; the api needed the second one too, so
|
|
129
|
+
// a merchant pressing Check now gets an answer in the request rather than a
|
|
130
|
+
// timestamp that a change stream turns into a job that answers a socket later.
|
|
131
|
+
// One copy, both callers, same shape back.
|
|
132
|
+
//
|
|
133
|
+
// `via` is the SendGrid-level call, injectable: sync's identity worker stubs
|
|
134
|
+
// exactly that boundary (its tests assert on the `path`), and tsup's exports
|
|
135
|
+
// are read-only so a test cannot reassign one.
|
|
136
|
+
const domain = {
|
|
137
|
+
|
|
138
|
+
// automatic_security is what makes this CNAME-based: the provider holds the
|
|
139
|
+
// keys and the merchant publishes three CNAMEs. Without it they paste DKIM
|
|
140
|
+
// keys into TXT records by hand.
|
|
141
|
+
register : async ({ apiKey, domain : name, via = sendgridRequest }) => {
|
|
142
|
+
|
|
143
|
+
const created = await via({
|
|
144
|
+
apiKey,
|
|
145
|
+
body : {
|
|
146
|
+
automatic_security : true,
|
|
147
|
+
domain : name
|
|
148
|
+
},
|
|
149
|
+
method : 'POST',
|
|
150
|
+
path : '/v3/whitelabel/domains'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
records : dnsRows( created?.dns ),
|
|
155
|
+
reference : created?.id == null ? null : String( created.id )
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Asks SendGrid to look the records up now. `records` carries per-record
|
|
161
|
+
// validity so a UI can say WHICH record has not landed rather than only that
|
|
162
|
+
// something has not; `valid` is the whole domain's verdict.
|
|
163
|
+
validate : async ({ apiKey, reference, via = sendgridRequest }) => {
|
|
164
|
+
|
|
165
|
+
const result = await via({
|
|
166
|
+
apiKey,
|
|
167
|
+
method : 'POST',
|
|
168
|
+
path : '/v3/whitelabel/domains/' + encodeURIComponent( reference ) + '/validate'
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
records : dnsRows( result?.validation_results ),
|
|
173
|
+
valid : Boolean( result?.valid )
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
};
|
|
179
|
+
|
|
112
180
|
const sendgrid = {
|
|
113
181
|
|
|
182
|
+
domain,
|
|
183
|
+
|
|
184
|
+
|
|
114
185
|
// `headers` (optional) carries the List-Unsubscribe pair on lead-facing
|
|
115
186
|
// commercial sends; omitted, the request body is byte-identical to the
|
|
116
187
|
// pre-opt-out-floor shape so system mail is untouched.
|
|
@@ -210,4 +281,4 @@ const sendgrid = {
|
|
|
210
281
|
// domain-authentication endpoints (/v3/whitelabel/domains) through the same
|
|
211
282
|
// authenticated client the mail send uses, rather than minting a second one.
|
|
212
283
|
|
|
213
|
-
export { sendWithRetry, sendgrid, sendgridRequest };
|
|
284
|
+
export { dnsRows, domain, sendWithRetry, sendgrid, sendgridRequest };
|
package/dist/sendgrid.js
CHANGED
|
@@ -84,7 +84,48 @@ var sendgridRequest = ({ apiKey, body, method, path, query, request: request2 =
|
|
|
84
84
|
url: SENDGRID_BASE + path
|
|
85
85
|
});
|
|
86
86
|
};
|
|
87
|
+
var dnsRows = (dns) => Object.values(dns || {}).filter((row) => (row == null ? void 0 : row.host) && (row == null ? void 0 : row.type)).map((row) => ({
|
|
88
|
+
data: String(row.data ?? ""),
|
|
89
|
+
host: String(row.host),
|
|
90
|
+
type: String(row.type),
|
|
91
|
+
valid: typeof row.valid === "boolean" ? row.valid : null
|
|
92
|
+
}));
|
|
93
|
+
var domain = {
|
|
94
|
+
// automatic_security is what makes this CNAME-based: the provider holds the
|
|
95
|
+
// keys and the merchant publishes three CNAMEs. Without it they paste DKIM
|
|
96
|
+
// keys into TXT records by hand.
|
|
97
|
+
register: async ({ apiKey, domain: name, via = sendgridRequest }) => {
|
|
98
|
+
const created = await via({
|
|
99
|
+
apiKey,
|
|
100
|
+
body: {
|
|
101
|
+
automatic_security: true,
|
|
102
|
+
domain: name
|
|
103
|
+
},
|
|
104
|
+
method: "POST",
|
|
105
|
+
path: "/v3/whitelabel/domains"
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
records: dnsRows(created == null ? void 0 : created.dns),
|
|
109
|
+
reference: (created == null ? void 0 : created.id) == null ? null : String(created.id)
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
// Asks SendGrid to look the records up now. `records` carries per-record
|
|
113
|
+
// validity so a UI can say WHICH record has not landed rather than only that
|
|
114
|
+
// something has not; `valid` is the whole domain's verdict.
|
|
115
|
+
validate: async ({ apiKey, reference, via = sendgridRequest }) => {
|
|
116
|
+
const result = await via({
|
|
117
|
+
apiKey,
|
|
118
|
+
method: "POST",
|
|
119
|
+
path: "/v3/whitelabel/domains/" + encodeURIComponent(reference) + "/validate"
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
records: dnsRows(result == null ? void 0 : result.validation_results),
|
|
123
|
+
valid: Boolean(result == null ? void 0 : result.valid)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
};
|
|
87
127
|
var sendgrid = {
|
|
128
|
+
domain,
|
|
88
129
|
// `headers` (optional) carries the List-Unsubscribe pair on lead-facing
|
|
89
130
|
// commercial sends; omitted, the request body is byte-identical to the
|
|
90
131
|
// pre-opt-out-floor shape so system mail is untouched.
|
|
@@ -149,6 +190,8 @@ var sendgrid = {
|
|
|
149
190
|
}
|
|
150
191
|
};
|
|
151
192
|
export {
|
|
193
|
+
dnsRows,
|
|
194
|
+
domain,
|
|
152
195
|
sendWithRetry,
|
|
153
196
|
sendgrid,
|
|
154
197
|
sendgridRequest
|
package/dist/twilio.cjs
CHANGED
|
@@ -71,6 +71,12 @@ var twilio = {
|
|
|
71
71
|
sms: async ({
|
|
72
72
|
accountSid,
|
|
73
73
|
authToken,
|
|
74
|
+
// WHO IS TEXTING. A platform number carries no sender name, so the body is
|
|
75
|
+
// the only place a recipient learns who this is from — and a text from a
|
|
76
|
+
// bare number with no name is the exact shape of smishing, which carriers
|
|
77
|
+
// filter on and people delete. Lead-facing sends pass the organization's
|
|
78
|
+
// title; OTC/auth sends pass nothing and keep their exact copy.
|
|
79
|
+
brand,
|
|
74
80
|
from,
|
|
75
81
|
link,
|
|
76
82
|
message,
|
|
@@ -83,7 +89,9 @@ var twilio = {
|
|
|
83
89
|
}) => {
|
|
84
90
|
const missing = Object.entries({ accountSid, authToken, from }).filter(([, value]) => !value).map(([name]) => name);
|
|
85
91
|
if (missing.length) throw new Error("Twilio credentials missing \u2014 " + missing.join(", ") + " (the drawbridge provider's smsSid, smsToken and smsFrom)");
|
|
92
|
+
const named = brand && String(title || "").trim().toLowerCase().startsWith(String(brand).trim().toLowerCase());
|
|
86
93
|
const body = [
|
|
94
|
+
...brand && !named ? [brand] : [],
|
|
87
95
|
title,
|
|
88
96
|
message,
|
|
89
97
|
...prize ? [prize.title + " (" + prize.value + ")"] : [],
|
package/dist/twilio.d.cts
CHANGED
|
@@ -15,6 +15,12 @@ const twilio = {
|
|
|
15
15
|
sms : async ({
|
|
16
16
|
accountSid,
|
|
17
17
|
authToken,
|
|
18
|
+
// WHO IS TEXTING. A platform number carries no sender name, so the body is
|
|
19
|
+
// the only place a recipient learns who this is from — and a text from a
|
|
20
|
+
// bare number with no name is the exact shape of smishing, which carriers
|
|
21
|
+
// filter on and people delete. Lead-facing sends pass the organization's
|
|
22
|
+
// title; OTC/auth sends pass nothing and keep their exact copy.
|
|
23
|
+
brand,
|
|
18
24
|
from,
|
|
19
25
|
link,
|
|
20
26
|
message,
|
|
@@ -38,12 +44,23 @@ const twilio = {
|
|
|
38
44
|
|
|
39
45
|
if( missing.length ) throw new Error( 'Twilio credentials missing — ' + missing.join( ', ' ) + ' (the drawbridge provider\'s smsSid, smsToken and smsFrom)' );
|
|
40
46
|
|
|
41
|
-
// Newline-separated,
|
|
47
|
+
// Newline-separated, BRAND FIRST, then title, and no "Your prize is" /
|
|
42
48
|
// "Click here to verify your prize" phrasing — textbook carrier-filter
|
|
43
49
|
// bait that got these messages flagged. The opt-out line is appended
|
|
44
50
|
// on lead-facing sends (carrier compliance); OTC/auth sends pass
|
|
45
51
|
// nothing new and keep their exact copy.
|
|
52
|
+
//
|
|
53
|
+
// The brand line is what the toll-free verification sample below
|
|
54
|
+
// promised Twilio we send — "Cedar & Sage / Thanks for entering… / Reply
|
|
55
|
+
// STOP to opt out" — and for a while it was the one line the real sends
|
|
56
|
+
// left out. It was a merchant's job to type it into the subject, and a
|
|
57
|
+
// warning nagged them when they did not; now it is put here, once, and
|
|
58
|
+
// the warning is gone. Skipped when the title already opens with it, so
|
|
59
|
+
// a merchant who wrote "Cedar & Sage: your prize" is not branded twice.
|
|
60
|
+
const named = brand && String( title || '' ).trim().toLowerCase().startsWith( String( brand ).trim().toLowerCase() );
|
|
61
|
+
|
|
46
62
|
const body = [
|
|
63
|
+
...( brand && ! named ? [ brand ] : [] ),
|
|
47
64
|
title,
|
|
48
65
|
message,
|
|
49
66
|
...( prize ? [ prize.title + ' (' + prize.value + ')' ] : [] ),
|
package/dist/twilio.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ const twilio = {
|
|
|
15
15
|
sms : async ({
|
|
16
16
|
accountSid,
|
|
17
17
|
authToken,
|
|
18
|
+
// WHO IS TEXTING. A platform number carries no sender name, so the body is
|
|
19
|
+
// the only place a recipient learns who this is from — and a text from a
|
|
20
|
+
// bare number with no name is the exact shape of smishing, which carriers
|
|
21
|
+
// filter on and people delete. Lead-facing sends pass the organization's
|
|
22
|
+
// title; OTC/auth sends pass nothing and keep their exact copy.
|
|
23
|
+
brand,
|
|
18
24
|
from,
|
|
19
25
|
link,
|
|
20
26
|
message,
|
|
@@ -38,12 +44,23 @@ const twilio = {
|
|
|
38
44
|
|
|
39
45
|
if( missing.length ) throw new Error( 'Twilio credentials missing — ' + missing.join( ', ' ) + ' (the drawbridge provider\'s smsSid, smsToken and smsFrom)' );
|
|
40
46
|
|
|
41
|
-
// Newline-separated,
|
|
47
|
+
// Newline-separated, BRAND FIRST, then title, and no "Your prize is" /
|
|
42
48
|
// "Click here to verify your prize" phrasing — textbook carrier-filter
|
|
43
49
|
// bait that got these messages flagged. The opt-out line is appended
|
|
44
50
|
// on lead-facing sends (carrier compliance); OTC/auth sends pass
|
|
45
51
|
// nothing new and keep their exact copy.
|
|
52
|
+
//
|
|
53
|
+
// The brand line is what the toll-free verification sample below
|
|
54
|
+
// promised Twilio we send — "Cedar & Sage / Thanks for entering… / Reply
|
|
55
|
+
// STOP to opt out" — and for a while it was the one line the real sends
|
|
56
|
+
// left out. It was a merchant's job to type it into the subject, and a
|
|
57
|
+
// warning nagged them when they did not; now it is put here, once, and
|
|
58
|
+
// the warning is gone. Skipped when the title already opens with it, so
|
|
59
|
+
// a merchant who wrote "Cedar & Sage: your prize" is not branded twice.
|
|
60
|
+
const named = brand && String( title || '' ).trim().toLowerCase().startsWith( String( brand ).trim().toLowerCase() );
|
|
61
|
+
|
|
46
62
|
const body = [
|
|
63
|
+
...( brand && ! named ? [ brand ] : [] ),
|
|
47
64
|
title,
|
|
48
65
|
message,
|
|
49
66
|
...( prize ? [ prize.title + ' (' + prize.value + ')' ] : [] ),
|
package/dist/twilio.js
CHANGED
|
@@ -46,6 +46,12 @@ var twilio = {
|
|
|
46
46
|
sms: async ({
|
|
47
47
|
accountSid,
|
|
48
48
|
authToken,
|
|
49
|
+
// WHO IS TEXTING. A platform number carries no sender name, so the body is
|
|
50
|
+
// the only place a recipient learns who this is from — and a text from a
|
|
51
|
+
// bare number with no name is the exact shape of smishing, which carriers
|
|
52
|
+
// filter on and people delete. Lead-facing sends pass the organization's
|
|
53
|
+
// title; OTC/auth sends pass nothing and keep their exact copy.
|
|
54
|
+
brand,
|
|
49
55
|
from,
|
|
50
56
|
link,
|
|
51
57
|
message,
|
|
@@ -58,7 +64,9 @@ var twilio = {
|
|
|
58
64
|
}) => {
|
|
59
65
|
const missing = Object.entries({ accountSid, authToken, from }).filter(([, value]) => !value).map(([name]) => name);
|
|
60
66
|
if (missing.length) throw new Error("Twilio credentials missing \u2014 " + missing.join(", ") + " (the drawbridge provider's smsSid, smsToken and smsFrom)");
|
|
67
|
+
const named = brand && String(title || "").trim().toLowerCase().startsWith(String(brand).trim().toLowerCase());
|
|
61
68
|
const body = [
|
|
69
|
+
...brand && !named ? [brand] : [],
|
|
62
70
|
title,
|
|
63
71
|
message,
|
|
64
72
|
...prize ? [prize.title + " (" + prize.value + ")"] : [],
|
package/package.json
CHANGED