@drawbridge/drawbridge-utils 0.0.112 → 0.0.115

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.
@@ -0,0 +1,168 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // lib/sendgrid.js
20
+ var sendgrid_exports = {};
21
+ __export(sendgrid_exports, {
22
+ sendWithRetry: () => sendWithRetry,
23
+ sendgrid: () => sendgrid,
24
+ sendgridRequest: () => sendgridRequest
25
+ });
26
+ module.exports = __toCommonJS(sendgrid_exports);
27
+
28
+ // lib/http.js
29
+ var DEFAULT_TIMEOUT_MS = 15e3;
30
+ var request = async ({
31
+ body,
32
+ headers = {},
33
+ method = "GET",
34
+ query,
35
+ timeout = DEFAULT_TIMEOUT_MS,
36
+ type = "json",
37
+ url
38
+ }) => {
39
+ const fullUrl = new URL(url);
40
+ if (query) {
41
+ Object.entries(query).forEach(([k, v]) => fullUrl.searchParams.set(k, v));
42
+ }
43
+ ;
44
+ const isForm = type === "form";
45
+ const response = await fetch(fullUrl.toString(), {
46
+ method,
47
+ headers: {
48
+ "Content-Type": isForm ? "application/x-www-form-urlencoded" : "application/json",
49
+ ...headers
50
+ },
51
+ signal: AbortSignal.timeout(timeout),
52
+ ...body !== void 0 && {
53
+ body: isForm ? new URLSearchParams(body).toString() : JSON.stringify(body)
54
+ }
55
+ });
56
+ if (!response.ok) {
57
+ const text2 = await response.text().catch(() => "");
58
+ const error = new Error(text2 || response.statusText);
59
+ error.status = response.status;
60
+ throw error;
61
+ }
62
+ ;
63
+ const text = await response.text();
64
+ try {
65
+ return text ? JSON.parse(text) : null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ };
70
+
71
+ // lib/sendgrid.js
72
+ var import_drawbridge_telemetry = require("@drawbridge/drawbridge-telemetry");
73
+ var logger = (0, import_drawbridge_telemetry.createLogger)();
74
+ var SENDGRID_BASE = "https://api.sendgrid.com";
75
+ var RETRYABLE_STATUSES = [429, 500, 502, 503];
76
+ var RETRY_DELAYS_MS = [1e3, 4e3];
77
+ var sendWithRetry = async (send, { delays = RETRY_DELAYS_MS } = {}) => {
78
+ for (let attempt = 0; ; attempt++) {
79
+ try {
80
+ return await send();
81
+ } catch (error) {
82
+ const status = Number(error == null ? void 0 : error.status);
83
+ const retryable = RETRYABLE_STATUSES.includes(status);
84
+ if (!retryable || attempt >= delays.length) {
85
+ if (!status) {
86
+ logger.warn("email.send.ambiguous-failure", {
87
+ name: error == null ? void 0 : error.name,
88
+ message: error == null ? void 0 : error.message
89
+ });
90
+ }
91
+ throw error;
92
+ }
93
+ logger.info("email.send.retry", {
94
+ attempt: attempt + 1,
95
+ status,
96
+ delayMs: delays[attempt]
97
+ });
98
+ await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
99
+ }
100
+ }
101
+ };
102
+ var sendgridRequest = ({ apiKey, body, method, path, query, request: request2 = request }) => {
103
+ const key = apiKey || process.env.SENDGRID_API_KEY;
104
+ return request2({
105
+ body,
106
+ headers: {
107
+ "Authorization": "Bearer " + key
108
+ },
109
+ method,
110
+ query,
111
+ url: SENDGRID_BASE + path
112
+ });
113
+ };
114
+ var sendgrid = {
115
+ // `headers` (optional) carries the List-Unsubscribe pair on lead-facing
116
+ // commercial sends; omitted, the request body is byte-identical to the
117
+ // pre-opt-out-floor shape so system mail is untouched.
118
+ send: async ({ apiKey, from, headers, html, request: request2, subject, text, to }) => {
119
+ var _a, _b;
120
+ try {
121
+ const sender = (from == null ? void 0 : from.email) ? from : {
122
+ email: process.env.SENDGRID_FROM_ADDRESS,
123
+ name: "Drawbridge"
124
+ };
125
+ await sendWithRetry(() => sendgridRequest({
126
+ apiKey,
127
+ ...request2 && { request: request2 },
128
+ method: "POST",
129
+ path: "/v3/mail/send",
130
+ body: {
131
+ content: [
132
+ {
133
+ type: "text/plain",
134
+ value: text
135
+ },
136
+ {
137
+ type: "text/html",
138
+ value: html
139
+ }
140
+ ],
141
+ from: sender,
142
+ ...headers && { headers },
143
+ personalizations: [
144
+ {
145
+ to: [
146
+ { email: to }
147
+ ]
148
+ }
149
+ ],
150
+ subject
151
+ }
152
+ }));
153
+ } catch (error) {
154
+ try {
155
+ const parsed = JSON.parse(error.message);
156
+ if ((_b = (_a = parsed == null ? void 0 : parsed.errors) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) error.message = parsed.errors[0].message;
157
+ } catch {
158
+ }
159
+ throw error;
160
+ }
161
+ }
162
+ };
163
+ // Annotate the CommonJS export names for ESM import in node:
164
+ 0 && (module.exports = {
165
+ sendWithRetry,
166
+ sendgrid,
167
+ sendgridRequest
168
+ });
@@ -0,0 +1,185 @@
1
+ import { request } from './http.cjs';
2
+ import { createLogger } from '@drawbridge/drawbridge-telemetry';
3
+
4
+ // SENDGRID TRANSPORT. Vendor HTTP and nothing else — no controller, no queue,
5
+ // no database.
6
+ //
7
+ // HERE rather than in drawbridge-sync because every other vendor's transport is
8
+ // here: Klaviyo's api(), Shopify's signature verification, Mailchimp's fetch.
9
+ // SendGrid was the one exception, and Drawbridge is a connection now like any
10
+ // other — the private `drawbridge` manifest declares email.send, email.notify
11
+ // and email.digest, so its transport belongs beside its manifest.
12
+ //
13
+ // The API key still falls back to process.env, which is a carry-over rather than
14
+ // a preference: a published package reading a deployment's environment is the
15
+ // thing `client : { id : 'KLAVIYO_OAUTH_CLIENT_ID' }` exists to avoid. Callers
16
+ // that pass a key explicitly never touch it.
17
+
18
+
19
+ const logger = createLogger();
20
+
21
+ const SENDGRID_BASE = 'https://api.sendgrid.com';
22
+
23
+ // --- provider-send retry -----------------------------------------------------
24
+ //
25
+ // Notification jobs are non-retryable BY DESIGN (non-idempotent $incs on the
26
+ // user-notification path), so a provider hiccup used to mean a silently
27
+ // dropped email. This retry wraps ONLY the provider POST, and only fires on
28
+ // DEFINITE pre-delivery failures — an HTTP status proving the provider
29
+ // REJECTED the request (rate limit / server error), where a retry can never
30
+ // double-send. Ambiguous outcomes (timeout, abort, connection reset — no
31
+ // status, because the request died in transit) are NOT retried: the provider
32
+ // may already have accepted the mail, and retrying risks a duplicate email in
33
+ // a real person's inbox. Those are logged and rethrown (plan guard-rail 12).
34
+ const RETRYABLE_STATUSES = [ 429, 500, 502, 503 ];
35
+
36
+ // 1s then 4s — long enough for a rate-limit window to roll over, short enough
37
+ // to keep the send inside the notification job's runtime budget.
38
+ const RETRY_DELAYS_MS = [ 1000, 4000 ];
39
+
40
+ const sendWithRetry = async ( send, { delays = RETRY_DELAYS_MS } = {} ) => {
41
+
42
+ for( let attempt = 0; ; attempt++ ){
43
+
44
+ try {
45
+
46
+ return await send();
47
+
48
+ } catch ( error ) {
49
+
50
+ const status = Number( error?.status );
51
+ const retryable = RETRYABLE_STATUSES.includes( status );
52
+
53
+ if( ! retryable || attempt >= delays.length ){
54
+
55
+ if( ! status ){
56
+
57
+ // No HTTP status = timeout / abort / reset — the ambiguous
58
+ // "maybe the provider accepted it" case. Surface it in logs
59
+ // so possible-but-unconfirmed sends are searchable.
60
+ logger.warn( 'email.send.ambiguous-failure', {
61
+ name : error?.name,
62
+ message : error?.message
63
+ });
64
+
65
+ }
66
+
67
+ throw error;
68
+
69
+ }
70
+
71
+ logger.info( 'email.send.retry', {
72
+ attempt : attempt + 1,
73
+ status,
74
+ delayMs : delays[ attempt ]
75
+ });
76
+
77
+ await new Promise( ( resolve ) => setTimeout( resolve, delays[ attempt ] ) );
78
+
79
+ }
80
+
81
+ }
82
+
83
+ };
84
+
85
+ // `request` is INJECTABLE, and that is not decoration. tsup emits this module's
86
+ // exports as getter-only, non-configurable properties, so the stub-by-mutation
87
+ // trick that worked when this lived in drawbridge-sync silently no-ops here —
88
+ // the assignment appears to succeed and the real HTTP call goes out. Injection
89
+ // is how every other vendor call in this package is tested (`fetcher` on each
90
+ // manifest hook), and it is the only thing that works across the boundary.
91
+ const sendgridRequest = ({ apiKey, body, method, path, query, request: request$1 = request }) => {
92
+
93
+ const key = apiKey || process.env.SENDGRID_API_KEY;
94
+
95
+ return request$1({
96
+ body,
97
+ headers : {
98
+ 'Authorization' : 'Bearer ' + key
99
+ },
100
+ method,
101
+ query,
102
+ url : SENDGRID_BASE + path
103
+ });
104
+
105
+ };
106
+
107
+ const sendgrid = {
108
+
109
+ // `headers` (optional) carries the List-Unsubscribe pair on lead-facing
110
+ // commercial sends; omitted, the request body is byte-identical to the
111
+ // pre-opt-out-floor shape so system mail is untouched.
112
+ send : async ({ apiKey, from, headers, html, request, subject, text, to }) => {
113
+
114
+ try {
115
+
116
+ const sender = ( from?.email ?
117
+ from
118
+ :
119
+ {
120
+ email : process.env.SENDGRID_FROM_ADDRESS,
121
+ name : 'Drawbridge'
122
+ }
123
+ );
124
+
125
+ await sendWithRetry( () => sendgridRequest({
126
+ apiKey,
127
+ ...( request && { request }),
128
+ method : 'POST',
129
+ path : '/v3/mail/send',
130
+ body : {
131
+ content : [
132
+ {
133
+ type : 'text/plain',
134
+ value : text
135
+ },
136
+ {
137
+ type : 'text/html',
138
+ value : html
139
+ }
140
+ ],
141
+ from : sender,
142
+ ...( headers && { headers } ),
143
+ personalizations : [
144
+ {
145
+ to : [
146
+ { email : to }
147
+ ]
148
+ }
149
+ ],
150
+ subject
151
+ }
152
+ }) );
153
+
154
+ } catch ( error ) {
155
+
156
+ // SendGrid rejections carry a JSON body; surface its first message.
157
+ // Non-JSON failures (timeouts, resets) pass through untouched so the
158
+ // parse can't mask the real error, and error.status survives for the
159
+ // caller.
160
+ try {
161
+
162
+ const parsed = JSON.parse( error.message );
163
+
164
+ if( parsed?.errors?.[ 0 ]?.message ) error.message = parsed.errors[ 0 ].message;
165
+
166
+ } catch {}
167
+
168
+ throw error;
169
+
170
+ }
171
+
172
+ }
173
+
174
+ };
175
+
176
+ // The Mandrill sender that used to live here is gone. Drawbridge sends every
177
+ // lead-facing email from its own SendGrid account now, so there was no caller
178
+ // left — and the merchant `mailchimp` connection is becoming an audience sync,
179
+ // which speaks to the Marketing API rather than Mandrill's transactional one.
180
+
181
+ // sendgridRequest is exported so the sending-domain slice can call SendGrid's
182
+ // domain-authentication endpoints (/v3/whitelabel/domains) through the same
183
+ // authenticated client the mail send uses, rather than minting a second one.
184
+
185
+ export { sendWithRetry, sendgrid, sendgridRequest };
@@ -0,0 +1,185 @@
1
+ import { request } from './http.js';
2
+ import { createLogger } from '@drawbridge/drawbridge-telemetry';
3
+
4
+ // SENDGRID TRANSPORT. Vendor HTTP and nothing else — no controller, no queue,
5
+ // no database.
6
+ //
7
+ // HERE rather than in drawbridge-sync because every other vendor's transport is
8
+ // here: Klaviyo's api(), Shopify's signature verification, Mailchimp's fetch.
9
+ // SendGrid was the one exception, and Drawbridge is a connection now like any
10
+ // other — the private `drawbridge` manifest declares email.send, email.notify
11
+ // and email.digest, so its transport belongs beside its manifest.
12
+ //
13
+ // The API key still falls back to process.env, which is a carry-over rather than
14
+ // a preference: a published package reading a deployment's environment is the
15
+ // thing `client : { id : 'KLAVIYO_OAUTH_CLIENT_ID' }` exists to avoid. Callers
16
+ // that pass a key explicitly never touch it.
17
+
18
+
19
+ const logger = createLogger();
20
+
21
+ const SENDGRID_BASE = 'https://api.sendgrid.com';
22
+
23
+ // --- provider-send retry -----------------------------------------------------
24
+ //
25
+ // Notification jobs are non-retryable BY DESIGN (non-idempotent $incs on the
26
+ // user-notification path), so a provider hiccup used to mean a silently
27
+ // dropped email. This retry wraps ONLY the provider POST, and only fires on
28
+ // DEFINITE pre-delivery failures — an HTTP status proving the provider
29
+ // REJECTED the request (rate limit / server error), where a retry can never
30
+ // double-send. Ambiguous outcomes (timeout, abort, connection reset — no
31
+ // status, because the request died in transit) are NOT retried: the provider
32
+ // may already have accepted the mail, and retrying risks a duplicate email in
33
+ // a real person's inbox. Those are logged and rethrown (plan guard-rail 12).
34
+ const RETRYABLE_STATUSES = [ 429, 500, 502, 503 ];
35
+
36
+ // 1s then 4s — long enough for a rate-limit window to roll over, short enough
37
+ // to keep the send inside the notification job's runtime budget.
38
+ const RETRY_DELAYS_MS = [ 1000, 4000 ];
39
+
40
+ const sendWithRetry = async ( send, { delays = RETRY_DELAYS_MS } = {} ) => {
41
+
42
+ for( let attempt = 0; ; attempt++ ){
43
+
44
+ try {
45
+
46
+ return await send();
47
+
48
+ } catch ( error ) {
49
+
50
+ const status = Number( error?.status );
51
+ const retryable = RETRYABLE_STATUSES.includes( status );
52
+
53
+ if( ! retryable || attempt >= delays.length ){
54
+
55
+ if( ! status ){
56
+
57
+ // No HTTP status = timeout / abort / reset — the ambiguous
58
+ // "maybe the provider accepted it" case. Surface it in logs
59
+ // so possible-but-unconfirmed sends are searchable.
60
+ logger.warn( 'email.send.ambiguous-failure', {
61
+ name : error?.name,
62
+ message : error?.message
63
+ });
64
+
65
+ }
66
+
67
+ throw error;
68
+
69
+ }
70
+
71
+ logger.info( 'email.send.retry', {
72
+ attempt : attempt + 1,
73
+ status,
74
+ delayMs : delays[ attempt ]
75
+ });
76
+
77
+ await new Promise( ( resolve ) => setTimeout( resolve, delays[ attempt ] ) );
78
+
79
+ }
80
+
81
+ }
82
+
83
+ };
84
+
85
+ // `request` is INJECTABLE, and that is not decoration. tsup emits this module's
86
+ // exports as getter-only, non-configurable properties, so the stub-by-mutation
87
+ // trick that worked when this lived in drawbridge-sync silently no-ops here —
88
+ // the assignment appears to succeed and the real HTTP call goes out. Injection
89
+ // is how every other vendor call in this package is tested (`fetcher` on each
90
+ // manifest hook), and it is the only thing that works across the boundary.
91
+ const sendgridRequest = ({ apiKey, body, method, path, query, request: request$1 = request }) => {
92
+
93
+ const key = apiKey || process.env.SENDGRID_API_KEY;
94
+
95
+ return request$1({
96
+ body,
97
+ headers : {
98
+ 'Authorization' : 'Bearer ' + key
99
+ },
100
+ method,
101
+ query,
102
+ url : SENDGRID_BASE + path
103
+ });
104
+
105
+ };
106
+
107
+ const sendgrid = {
108
+
109
+ // `headers` (optional) carries the List-Unsubscribe pair on lead-facing
110
+ // commercial sends; omitted, the request body is byte-identical to the
111
+ // pre-opt-out-floor shape so system mail is untouched.
112
+ send : async ({ apiKey, from, headers, html, request, subject, text, to }) => {
113
+
114
+ try {
115
+
116
+ const sender = ( from?.email ?
117
+ from
118
+ :
119
+ {
120
+ email : process.env.SENDGRID_FROM_ADDRESS,
121
+ name : 'Drawbridge'
122
+ }
123
+ );
124
+
125
+ await sendWithRetry( () => sendgridRequest({
126
+ apiKey,
127
+ ...( request && { request }),
128
+ method : 'POST',
129
+ path : '/v3/mail/send',
130
+ body : {
131
+ content : [
132
+ {
133
+ type : 'text/plain',
134
+ value : text
135
+ },
136
+ {
137
+ type : 'text/html',
138
+ value : html
139
+ }
140
+ ],
141
+ from : sender,
142
+ ...( headers && { headers } ),
143
+ personalizations : [
144
+ {
145
+ to : [
146
+ { email : to }
147
+ ]
148
+ }
149
+ ],
150
+ subject
151
+ }
152
+ }) );
153
+
154
+ } catch ( error ) {
155
+
156
+ // SendGrid rejections carry a JSON body; surface its first message.
157
+ // Non-JSON failures (timeouts, resets) pass through untouched so the
158
+ // parse can't mask the real error, and error.status survives for the
159
+ // caller.
160
+ try {
161
+
162
+ const parsed = JSON.parse( error.message );
163
+
164
+ if( parsed?.errors?.[ 0 ]?.message ) error.message = parsed.errors[ 0 ].message;
165
+
166
+ } catch {}
167
+
168
+ throw error;
169
+
170
+ }
171
+
172
+ }
173
+
174
+ };
175
+
176
+ // The Mandrill sender that used to live here is gone. Drawbridge sends every
177
+ // lead-facing email from its own SendGrid account now, so there was no caller
178
+ // left — and the merchant `mailchimp` connection is becoming an audience sync,
179
+ // which speaks to the Marketing API rather than Mandrill's transactional one.
180
+
181
+ // sendgridRequest is exported so the sending-domain slice can call SendGrid's
182
+ // domain-authentication endpoints (/v3/whitelabel/domains) through the same
183
+ // authenticated client the mail send uses, rather than minting a second one.
184
+
185
+ export { sendWithRetry, sendgrid, sendgridRequest };
@@ -0,0 +1,140 @@
1
+ // lib/http.js
2
+ var DEFAULT_TIMEOUT_MS = 15e3;
3
+ var request = async ({
4
+ body,
5
+ headers = {},
6
+ method = "GET",
7
+ query,
8
+ timeout = DEFAULT_TIMEOUT_MS,
9
+ type = "json",
10
+ url
11
+ }) => {
12
+ const fullUrl = new URL(url);
13
+ if (query) {
14
+ Object.entries(query).forEach(([k, v]) => fullUrl.searchParams.set(k, v));
15
+ }
16
+ ;
17
+ const isForm = type === "form";
18
+ const response = await fetch(fullUrl.toString(), {
19
+ method,
20
+ headers: {
21
+ "Content-Type": isForm ? "application/x-www-form-urlencoded" : "application/json",
22
+ ...headers
23
+ },
24
+ signal: AbortSignal.timeout(timeout),
25
+ ...body !== void 0 && {
26
+ body: isForm ? new URLSearchParams(body).toString() : JSON.stringify(body)
27
+ }
28
+ });
29
+ if (!response.ok) {
30
+ const text2 = await response.text().catch(() => "");
31
+ const error = new Error(text2 || response.statusText);
32
+ error.status = response.status;
33
+ throw error;
34
+ }
35
+ ;
36
+ const text = await response.text();
37
+ try {
38
+ return text ? JSON.parse(text) : null;
39
+ } catch {
40
+ return null;
41
+ }
42
+ };
43
+
44
+ // lib/sendgrid.js
45
+ import { createLogger } from "@drawbridge/drawbridge-telemetry";
46
+ var logger = createLogger();
47
+ var SENDGRID_BASE = "https://api.sendgrid.com";
48
+ var RETRYABLE_STATUSES = [429, 500, 502, 503];
49
+ var RETRY_DELAYS_MS = [1e3, 4e3];
50
+ var sendWithRetry = async (send, { delays = RETRY_DELAYS_MS } = {}) => {
51
+ for (let attempt = 0; ; attempt++) {
52
+ try {
53
+ return await send();
54
+ } catch (error) {
55
+ const status = Number(error == null ? void 0 : error.status);
56
+ const retryable = RETRYABLE_STATUSES.includes(status);
57
+ if (!retryable || attempt >= delays.length) {
58
+ if (!status) {
59
+ logger.warn("email.send.ambiguous-failure", {
60
+ name: error == null ? void 0 : error.name,
61
+ message: error == null ? void 0 : error.message
62
+ });
63
+ }
64
+ throw error;
65
+ }
66
+ logger.info("email.send.retry", {
67
+ attempt: attempt + 1,
68
+ status,
69
+ delayMs: delays[attempt]
70
+ });
71
+ await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
72
+ }
73
+ }
74
+ };
75
+ var sendgridRequest = ({ apiKey, body, method, path, query, request: request2 = request }) => {
76
+ const key = apiKey || process.env.SENDGRID_API_KEY;
77
+ return request2({
78
+ body,
79
+ headers: {
80
+ "Authorization": "Bearer " + key
81
+ },
82
+ method,
83
+ query,
84
+ url: SENDGRID_BASE + path
85
+ });
86
+ };
87
+ var sendgrid = {
88
+ // `headers` (optional) carries the List-Unsubscribe pair on lead-facing
89
+ // commercial sends; omitted, the request body is byte-identical to the
90
+ // pre-opt-out-floor shape so system mail is untouched.
91
+ send: async ({ apiKey, from, headers, html, request: request2, subject, text, to }) => {
92
+ var _a, _b;
93
+ try {
94
+ const sender = (from == null ? void 0 : from.email) ? from : {
95
+ email: process.env.SENDGRID_FROM_ADDRESS,
96
+ name: "Drawbridge"
97
+ };
98
+ await sendWithRetry(() => sendgridRequest({
99
+ apiKey,
100
+ ...request2 && { request: request2 },
101
+ method: "POST",
102
+ path: "/v3/mail/send",
103
+ body: {
104
+ content: [
105
+ {
106
+ type: "text/plain",
107
+ value: text
108
+ },
109
+ {
110
+ type: "text/html",
111
+ value: html
112
+ }
113
+ ],
114
+ from: sender,
115
+ ...headers && { headers },
116
+ personalizations: [
117
+ {
118
+ to: [
119
+ { email: to }
120
+ ]
121
+ }
122
+ ],
123
+ subject
124
+ }
125
+ }));
126
+ } catch (error) {
127
+ try {
128
+ const parsed = JSON.parse(error.message);
129
+ if ((_b = (_a = parsed == null ? void 0 : parsed.errors) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) error.message = parsed.errors[0].message;
130
+ } catch {
131
+ }
132
+ throw error;
133
+ }
134
+ }
135
+ };
136
+ export {
137
+ sendWithRetry,
138
+ sendgrid,
139
+ sendgridRequest
140
+ };