@softure-ai/mailing 0.2.0 → 0.2.2
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/CHANGELOG.md +48 -0
- package/README.md +85 -6
- package/dist/contract.d.ts +13 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/jobs/delivery-job.d.ts +72 -0
- package/dist/jobs/delivery-job.d.ts.map +1 -0
- package/dist/jobs/delivery-job.js +85 -0
- package/dist/jobs/delivery-job.js.map +1 -0
- package/dist/jobs/index.d.ts +2 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +4 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/messages/en.d.ts +2 -0
- package/dist/messages/en.d.ts.map +1 -1
- package/dist/messages/en.js +2 -0
- package/dist/messages/en.js.map +1 -1
- package/dist/messages/index.d.ts +4 -2
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/next/actions.d.ts.map +1 -1
- package/dist/next/actions.js +8 -26
- package/dist/next/actions.js.map +1 -1
- package/dist/next/page-submit.d.ts +12 -0
- package/dist/next/page-submit.d.ts.map +1 -0
- package/dist/next/page-submit.js +30 -0
- package/dist/next/page-submit.js.map +1 -0
- package/dist/next/pages.d.ts.map +1 -1
- package/dist/next/pages.js +8 -5
- package/dist/next/pages.js.map +1 -1
- package/dist/next/route.d.ts +4 -1
- package/dist/next/route.d.ts.map +1 -1
- package/dist/next/route.js +58 -2
- package/dist/next/route.js.map +1 -1
- package/dist/next/strip-link-query.d.ts +11 -0
- package/dist/next/strip-link-query.d.ts.map +1 -0
- package/dist/next/strip-link-query.js +25 -0
- package/dist/next/strip-link-query.js.map +1 -0
- package/dist/providers/resend.d.ts +5 -0
- package/dist/providers/resend.d.ts.map +1 -1
- package/dist/providers/resend.js +23 -3
- package/dist/providers/resend.js.map +1 -1
- package/dist/server/deliveries.d.ts +6 -1
- package/dist/server/deliveries.d.ts.map +1 -1
- package/dist/server/deliveries.js +3 -2
- package/dist/server/deliveries.js.map +1 -1
- package/dist/server/send-mail.d.ts +2 -1
- package/dist/server/send-mail.d.ts.map +1 -1
- package/dist/server/send-mail.js +17 -5
- package/dist/server/send-mail.js.map +1 -1
- package/module.json +2 -2
- package/package.json +12 -1
- package/src/contract.ts +14 -3
- package/src/index.ts +4 -3
- package/src/jobs/delivery-job.ts +129 -0
- package/src/jobs/index.ts +12 -0
- package/src/messages/en.ts +2 -0
- package/src/messages/index.ts +2 -2
- package/src/next/actions.ts +8 -24
- package/src/next/page-submit.ts +35 -0
- package/src/next/pages.tsx +9 -5
- package/src/next/route.ts +59 -4
- package/src/next/strip-link-query.tsx +30 -0
- package/src/providers/resend.ts +22 -3
- package/src/server/deliveries.ts +9 -5
- package/src/server/send-mail.ts +18 -5
package/dist/next/actions.js
CHANGED
|
@@ -1,35 +1,17 @@
|
|
|
1
1
|
"use server";
|
|
2
|
-
// The unsubscribe
|
|
3
|
-
// the
|
|
4
|
-
//
|
|
5
|
-
// the
|
|
6
|
-
|
|
2
|
+
// The unsubscribe server action, for an app whose own form posts to it: it takes the signed link
|
|
3
|
+
// from the form body, records the opt-out and redirects to the page's outcome. The package page
|
|
4
|
+
// does not use it: a server action posts to the page's URL, which carries the link, so the page's
|
|
5
|
+
// form posts to the one-click route's bare path instead (route.ts). Next refuses an action whose
|
|
6
|
+
// Origin does not match the host.
|
|
7
7
|
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
8
8
|
import { redirect } from "next/navigation";
|
|
9
|
-
import {
|
|
10
|
-
import { unsubscribe } from "../server/suppressions.js";
|
|
11
|
-
import { getUnsubscribeLinkParams, readUnsubscribeLink } from "../server/unsubscribe-link.js";
|
|
9
|
+
import { readUnsubscribeLink } from "../server/unsubscribe-link.js";
|
|
12
10
|
import { getMailingContext } from "./context.js";
|
|
13
|
-
import {
|
|
11
|
+
import { submitPageUnsubscribe } from "./page-submit.js";
|
|
14
12
|
export async function unsubscribeAction(formData) {
|
|
15
13
|
const config = getSoftureConfig();
|
|
16
14
|
const link = readUnsubscribeLink(formData, config);
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const result = await unsubscribe(await getMailingContext(config), link, "page");
|
|
20
|
-
status = result.ok ? "done" : "invalid";
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
// The label names the kind of failure, never its text: that can carry the link's values.
|
|
24
|
-
console.error(`@softure-ai/mailing: unsubscribe failed: ${errorLogLabel(error)}`);
|
|
25
|
-
status = "failed";
|
|
26
|
-
}
|
|
27
|
-
const query = new URLSearchParams({ [UNSUBSCRIBE_STATUS_PARAM]: status });
|
|
28
|
-
if (status === "failed" && link !== null) {
|
|
29
|
-
// The form comes back with the same link, so the person can try again.
|
|
30
|
-
for (const [name, value] of Object.entries(getUnsubscribeLinkParams(link)))
|
|
31
|
-
query.set(name, value);
|
|
32
|
-
}
|
|
33
|
-
redirect(`${getMailingRoutes(config).unsubscribe}?${query.toString()}`);
|
|
15
|
+
redirect(await submitPageUnsubscribe({ config, link, getContext: () => getMailingContext(config) }));
|
|
34
16
|
}
|
|
35
17
|
//# sourceMappingURL=actions.js.map
|
package/dist/next/actions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/next/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,gGAAgG;AAChG,
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/next/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,iGAAiG;AACjG,gGAAgG;AAChG,kGAAkG;AAClG,iGAAiG;AACjG,kCAAkC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAkB;IACxD,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,qBAAqB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AACvG,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type SoftureConfig } from "@softure-ai/core";
|
|
2
|
+
import { type SuppressionContext } from "../server/suppressions.js";
|
|
3
|
+
import { type UnsubscribeLink } from "../server/unsubscribe-link.js";
|
|
4
|
+
export interface PageSubmit {
|
|
5
|
+
readonly config: SoftureConfig;
|
|
6
|
+
readonly link: UnsubscribeLink | null;
|
|
7
|
+
/** Opens the database; a failure there is a failed submit like any other. */
|
|
8
|
+
readonly getContext: () => Promise<SuppressionContext>;
|
|
9
|
+
}
|
|
10
|
+
/** Records the link from the page and returns the page's outcome URL (a path with its query). */
|
|
11
|
+
export declare function submitPageUnsubscribe({ config, link, getContext }: PageSubmit): Promise<string>;
|
|
12
|
+
//# sourceMappingURL=page-submit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-submit.d.ts","sourceRoot":"","sources":["../../src/next/page-submit.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAErE,OAAO,EAAe,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,EAA4B,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAG/F,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACxD;AAED,iGAAiG;AACjG,wBAAsB,qBAAqB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBrG"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// The page's submit, shared by the route (the package page's form posts there) and the server
|
|
2
|
+
// action (for an app's own form): record the opt-out and name the page's outcome to send the
|
|
3
|
+
// browser to. No session is involved: the signature is the authorization.
|
|
4
|
+
import { errorLogLabel } from "@softure-ai/core";
|
|
5
|
+
import { getMailingRoutes } from "../server/options.js";
|
|
6
|
+
import { unsubscribe } from "../server/suppressions.js";
|
|
7
|
+
import { getUnsubscribeLinkParams } from "../server/unsubscribe-link.js";
|
|
8
|
+
import { UNSUBSCRIBE_STATUS_PARAM } from "./params.js";
|
|
9
|
+
/** Records the link from the page and returns the page's outcome URL (a path with its query). */
|
|
10
|
+
export async function submitPageUnsubscribe({ config, link, getContext }) {
|
|
11
|
+
let status;
|
|
12
|
+
try {
|
|
13
|
+
const result = await unsubscribe(await getContext(), link, "page");
|
|
14
|
+
status = result.ok ? "done" : "invalid";
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
// The label names the kind of failure, never its text: that can carry the link's values.
|
|
18
|
+
console.error(`@softure-ai/mailing: unsubscribe failed: ${errorLogLabel(error)}`);
|
|
19
|
+
status = "failed";
|
|
20
|
+
}
|
|
21
|
+
const query = new URLSearchParams({ [UNSUBSCRIBE_STATUS_PARAM]: status });
|
|
22
|
+
if (status === "failed" && link !== null) {
|
|
23
|
+
// The form comes back with the same link, so the person can try again; the page strips it
|
|
24
|
+
// from the address bar again.
|
|
25
|
+
for (const [name, value] of Object.entries(getUnsubscribeLinkParams(link)))
|
|
26
|
+
query.set(name, value);
|
|
27
|
+
}
|
|
28
|
+
return `${getMailingRoutes(config).unsubscribe}?${query.toString()}`;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=page-submit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-submit.js","sourceRoot":"","sources":["../../src/next/page-submit.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,6FAA6F;AAC7F,0EAA0E;AAC1E,OAAO,EAAE,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,WAAW,EAA2B,MAAM,2BAA2B,CAAC;AACjF,OAAO,EAAE,wBAAwB,EAAwB,MAAM,+BAA+B,CAAC;AAC/F,OAAO,EAAE,wBAAwB,EAA0B,MAAM,aAAa,CAAC;AAS/E,iGAAiG;AACjG,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAc;IAClF,IAAI,MAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACnE,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,yFAAyF;QACzF,OAAO,CAAC,KAAK,CAAC,4CAA4C,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClF,MAAM,GAAG,QAAQ,CAAC;IACpB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,CAAC,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1E,IAAI,MAAM,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACzC,0FAA0F;QAC1F,8BAA8B;QAC9B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACrG,CAAC;IACD,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,WAAW,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;AACvE,CAAC"}
|
package/dist/next/pages.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAkD,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjG,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOtD,KAAK,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;AAE3E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;CACtC;AAED,uGAAuG;AACvG,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEtE,gGAAgG;AAChG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;CACzD;AA2CD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,IACvB,kBAAkB,oBAAoB,0CAGxF;AAED,wBAAsB,eAAe,CAAC,EAAE,YAAY,EAAE,EAAE,oBAAoB,wCAE3E"}
|
package/dist/next/pages.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// The unsubscribe page, ready to mount with one line:
|
|
3
3
|
// `export { UnsubscribePage as default } from "@softure-ai/mailing/next"`.
|
|
4
|
-
// A server component
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// A server component: the button is a plain form posting the link, in hidden fields, to the
|
|
5
|
+
// one-click route's bare path (route.ts), so it works without JavaScript in any browser a mail
|
|
6
|
+
// client opens and the signed token never sits in a POST's request line. With JavaScript a small
|
|
7
|
+
// client component also takes the link out of the address bar. Opening the page changes nothing
|
|
8
|
+
// (mail scanners open links too) and does not check the signature: the submit does. It takes the
|
|
7
9
|
// module's signed links and, with `mailing({ legacyUnsubscribe })`, the app's older ones: mount it
|
|
8
10
|
// at the old links' path too.
|
|
9
11
|
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
10
12
|
import { Button, Card, createSlotClassGetter, FormError } from "@softure-ai/ui";
|
|
13
|
+
import { getMailingRoutes } from "../server/options.js";
|
|
11
14
|
import { getUnsubscribeLinkParams, readUnsubscribeLink } from "../server/unsubscribe-link.js";
|
|
12
|
-
import { unsubscribeAction } from "./actions.js";
|
|
13
15
|
import { getMailingMessages } from "./messages.js";
|
|
14
16
|
import { UNSUBSCRIBE_STATUS_PARAM } from "./params.js";
|
|
17
|
+
import { StripLinkQuery } from "./strip-link-query.js";
|
|
15
18
|
const DEFAULT_CLASSES = {
|
|
16
19
|
root: "sft:mx-auto sft:box-border sft:w-full sft:sm:max-w-md sft:px-4 sft:py-4",
|
|
17
20
|
card: "",
|
|
@@ -60,6 +63,6 @@ async function renderUnsubscribePage(searchParams, options) {
|
|
|
60
63
|
return _jsx(UnsubscribeLayout, { title: messages.invalidTitle, lead: messages.invalidBody, slot: slot, options: options });
|
|
61
64
|
}
|
|
62
65
|
const submit = slot("submit");
|
|
63
|
-
return (
|
|
66
|
+
return (_jsxs(UnsubscribeLayout, { title: messages.title, lead: messages.lead, slot: slot, options: options, children: [_jsx(StripLinkQuery, { names: Object.keys(linkParams) }), _jsxs("form", { action: getMailingRoutes(config).oneClick, method: "post", className: slot("form"), children: [Object.entries(linkParams).map(([name, value]) => (_jsx("input", { type: "hidden", name: name, value: value }, name))), _jsx(FormError, { message: status === "failed" ? messages.failed : undefined, unstyled: options.unstyled }), _jsx("div", { children: _jsx(Button, { type: "submit", variant: "primary", unstyled: options.unstyled, ...(submit === undefined ? {} : { classNames: { root: submit } }), children: messages.submit }) })] })] }));
|
|
64
67
|
}
|
|
65
68
|
//# sourceMappingURL=pages.js.map
|
package/dist/next/pages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,2EAA2E;AAC3E,4FAA4F;AAC5F,iGAAiG;AACjG,
|
|
1
|
+
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":"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"}
|
package/dist/next/route.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Records the opt-out of the link in the URL.
|
|
2
|
+
* Records the opt-out of the link in the URL. Its body (`List-Unsubscribe=One-Click`) is not read:
|
|
3
3
|
* anyone can send it, the signature is the proof. 200 once recorded (also again), 400 for a link
|
|
4
4
|
* that does not verify (200 with `mailing({ oneClickInvalidLinkStatus: 200 })`, so the answer never
|
|
5
5
|
* tells whether a token is live), 500 when the database fails: the client may retry, and 200 would
|
|
6
6
|
* claim an opt-out that did not happen. A legacy link (`mailing({ legacyUnsubscribe })`) is handled the same
|
|
7
7
|
* way; mount this route at the old link's path too.
|
|
8
|
+
*
|
|
9
|
+
* A POST without a link in the URL is the unsubscribe page's form: its link comes from the form
|
|
10
|
+
* body, and the answer is a 303 to the page's outcome (`?status=done`, `invalid` or `failed`).
|
|
8
11
|
*/
|
|
9
12
|
export declare function postUnsubscribeRoute(request: Request): Promise<Response>;
|
|
10
13
|
/**
|
package/dist/next/route.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/next/route.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/next/route.ts"],"names":[],"mappings":"AAwBA;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmB9E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAG9D"}
|
package/dist/next/route.js
CHANGED
|
@@ -8,25 +8,42 @@
|
|
|
8
8
|
// purpose: a few provider addresses act for millions of people, so a bucket would refuse real
|
|
9
9
|
// unsubscribes. The link is checked by shape and signature before the database is touched, and
|
|
10
10
|
// the only write is an idempotent insert.
|
|
11
|
+
//
|
|
12
|
+
// The package page's form posts here too, to the bare path with the link in its body, so the
|
|
13
|
+
// signed token never sits in a POST's request line (a server action would post to the page's URL,
|
|
14
|
+
// query included). No Origin check, as for a mail client: the signature is the authorization.
|
|
11
15
|
import { errorLogLabel } from "@softure-ai/core";
|
|
12
16
|
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
13
17
|
import { getMailingOptions, getMailingRoutes } from "../server/options.js";
|
|
14
18
|
import { unsubscribe } from "../server/suppressions.js";
|
|
15
19
|
import { readUnsubscribeLink } from "../server/unsubscribe-link.js";
|
|
16
20
|
import { getMailingContext } from "./context.js";
|
|
21
|
+
import { submitPageUnsubscribe } from "./page-submit.js";
|
|
17
22
|
const NO_STORE = { "cache-control": "no-store" };
|
|
18
23
|
/**
|
|
19
|
-
* Records the opt-out of the link in the URL.
|
|
24
|
+
* Records the opt-out of the link in the URL. Its body (`List-Unsubscribe=One-Click`) is not read:
|
|
20
25
|
* anyone can send it, the signature is the proof. 200 once recorded (also again), 400 for a link
|
|
21
26
|
* that does not verify (200 with `mailing({ oneClickInvalidLinkStatus: 200 })`, so the answer never
|
|
22
27
|
* tells whether a token is live), 500 when the database fails: the client may retry, and 200 would
|
|
23
28
|
* claim an opt-out that did not happen. A legacy link (`mailing({ legacyUnsubscribe })`) is handled the same
|
|
24
29
|
* way; mount this route at the old link's path too.
|
|
30
|
+
*
|
|
31
|
+
* A POST without a link in the URL is the unsubscribe page's form: its link comes from the form
|
|
32
|
+
* body, and the answer is a 303 to the page's outcome (`?status=done`, `invalid` or `failed`).
|
|
25
33
|
*/
|
|
26
34
|
export async function postUnsubscribeRoute(request) {
|
|
35
|
+
const config = getSoftureConfig();
|
|
36
|
+
const { searchParams } = new URL(request.url);
|
|
37
|
+
if (readUnsubscribeLink(searchParams, config) === null) {
|
|
38
|
+
const formLink = await readFormLink(request, config);
|
|
39
|
+
if (formLink !== null) {
|
|
40
|
+
const location = await submitPageUnsubscribe({ config, link: formLink, getContext: () => getMailingContext(config) });
|
|
41
|
+
return new Response(null, { status: 303, headers: { ...NO_STORE, location } });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
27
44
|
try {
|
|
28
45
|
const ctx = await getMailingContext();
|
|
29
|
-
const link = readUnsubscribeLink(
|
|
46
|
+
const link = readUnsubscribeLink(searchParams, ctx.config);
|
|
30
47
|
const result = await unsubscribe(ctx, link, "one-click");
|
|
31
48
|
return new Response(null, { status: result.ok ? 200 : getMailingOptions(ctx.config).oneClickInvalidLinkStatus, headers: NO_STORE });
|
|
32
49
|
}
|
|
@@ -44,4 +61,43 @@ export function getUnsubscribeRoute(request) {
|
|
|
44
61
|
const { search } = new URL(request.url);
|
|
45
62
|
return new Response(null, { status: 303, headers: { ...NO_STORE, location: `${getMailingRoutes(getSoftureConfig()).unsubscribe}${search}` } });
|
|
46
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* The page's form is URL-encoded and small (a signed link is about 100 bytes; a legacy one at most a few 512-character
|
|
66
|
+
* values). A larger body is no page submit, and is not read past this size.
|
|
67
|
+
*/
|
|
68
|
+
const MAX_FORM_BYTES = 8192;
|
|
69
|
+
const FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
70
|
+
/** The link in a URL-encoded form body, or null for any other body. */
|
|
71
|
+
async function readFormLink(request, config) {
|
|
72
|
+
if (request.headers.get("content-type")?.toLowerCase().startsWith(FORM_CONTENT_TYPE) !== true)
|
|
73
|
+
return null;
|
|
74
|
+
const body = await readBoundedText(request, MAX_FORM_BYTES);
|
|
75
|
+
return body === null ? null : readUnsubscribeLink(new URLSearchParams(body), config);
|
|
76
|
+
}
|
|
77
|
+
/** The body as text, or null when it is longer than `maxBytes` (the rest is not read) or cannot be read. */
|
|
78
|
+
async function readBoundedText(request, maxBytes) {
|
|
79
|
+
if (request.body === null)
|
|
80
|
+
return "";
|
|
81
|
+
const reader = request.body.getReader();
|
|
82
|
+
const chunks = [];
|
|
83
|
+
let size = 0;
|
|
84
|
+
try {
|
|
85
|
+
for (;;) {
|
|
86
|
+
const { done, value } = await reader.read();
|
|
87
|
+
if (done)
|
|
88
|
+
break;
|
|
89
|
+
size += value.byteLength;
|
|
90
|
+
if (size > maxBytes) {
|
|
91
|
+
await reader.cancel();
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
chunks.push(value);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// A client that drops the connection mid-body: no form to read.
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
102
|
+
}
|
|
47
103
|
//# sourceMappingURL=route.js.map
|
package/dist/next/route.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/next/route.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,wCAAwC;AACxC,yGAAyG;AACzG,EAAE;AACF,6FAA6F;AAC7F,gGAAgG;AAChG,0FAA0F;AAC1F,8FAA8F;AAC9F,+FAA+F;AAC/F,0CAA0C;AAC1C,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/next/route.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface StripLinkQueryProps {
|
|
2
|
+
/** The query parameters that carry the link. */
|
|
3
|
+
readonly names: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function StripLinkQuery({ names }: StripLinkQueryProps): null;
|
|
6
|
+
/**
|
|
7
|
+
* Replaces the current history entry with the URL minus `names`. The current `history.state` goes
|
|
8
|
+
* along: it carries Next's own marker, so its router lets the call through without a navigation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stripQueryParams(names: readonly string[]): void;
|
|
11
|
+
//# sourceMappingURL=strip-link-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strip-link-query.d.ts","sourceRoot":"","sources":["../../src/next/strip-link-query.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE,mBAAmB,QAK5D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAM/D"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// Takes the unsubscribe link's parameters out of the address bar once the page has loaded, so the
|
|
3
|
+
// signed token does not stay in the tab's URL and the browser history. It renders nothing; without
|
|
4
|
+
// JavaScript the address bar keeps the link, and the form still works.
|
|
5
|
+
import { useEffect } from "react";
|
|
6
|
+
export function StripLinkQuery({ names }) {
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
stripQueryParams(names);
|
|
9
|
+
}, [names]);
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Replaces the current history entry with the URL minus `names`. The current `history.state` goes
|
|
14
|
+
* along: it carries Next's own marker, so its router lets the call through without a navigation.
|
|
15
|
+
*/
|
|
16
|
+
export function stripQueryParams(names) {
|
|
17
|
+
const url = new URL(location.href);
|
|
18
|
+
const present = names.filter((name) => url.searchParams.has(name));
|
|
19
|
+
if (present.length === 0)
|
|
20
|
+
return;
|
|
21
|
+
for (const name of present)
|
|
22
|
+
url.searchParams.delete(name);
|
|
23
|
+
history.replaceState(history.state, "", `${url.pathname}${url.search}${url.hash}`);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=strip-link-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strip-link-query.js","sourceRoot":"","sources":["../../src/next/strip-link-query.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,kGAAkG;AAClG,mGAAmG;AACnG,uEAAuE;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOlC,MAAM,UAAU,cAAc,CAAC,EAAE,KAAK,EAAuB;IAC3D,SAAS,CAAC,GAAG,EAAE;QACb,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAwB;IACvD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACjC,KAAK,MAAM,IAAI,IAAI,OAAO;QAAE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1D,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AACrF,CAAC"}
|
|
@@ -14,4 +14,9 @@ export interface ResendOptions {
|
|
|
14
14
|
}
|
|
15
15
|
/** Sends through Resend's HTTP API. */
|
|
16
16
|
export declare function resend(options?: ResendOptions): MailProvider;
|
|
17
|
+
/**
|
|
18
|
+
* Reads a `Retry-After` header (RFC 9110 §10.2.3): a whole number of seconds, or an HTTP date (IMF-fixdate) turned into the seconds
|
|
19
|
+
* from `nowMs` to it (0 for a date in the past). `null` when there is none or it is malformed.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readRetryAfter(value: string | null, nowMs: number): number | null;
|
|
17
22
|
//# sourceMappingURL=resend.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resend.d.ts","sourceRoot":"","sources":["../../src/providers/resend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAoC,MAAM,gBAAgB,CAAC;AAErF,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAC/D,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B;AAWD,uCAAuC;AACvC,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,YAAY,CA0BhE"}
|
|
1
|
+
{"version":3,"file":"resend.d.ts","sourceRoot":"","sources":["../../src/providers/resend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAoC,MAAM,gBAAgB,CAAC;AAErF,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAC/D,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B;AAWD,uCAAuC;AACvC,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,YAAY,CA0BhE;AAmCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOjF"}
|
package/dist/providers/resend.js
CHANGED
|
@@ -54,12 +54,32 @@ async function readOutcome(response) {
|
|
|
54
54
|
// Only the error's `name` (an enum) is read: its `message` can echo the address back.
|
|
55
55
|
const name = readString(payload, "name");
|
|
56
56
|
const httpStatus = response.status;
|
|
57
|
+
const retryAfterSeconds = readRetryAfter(response.headers.get("retry-after"), Date.now());
|
|
58
|
+
const failure = retryAfterSeconds === null ? { httpStatus } : { httpStatus, retryAfterSeconds };
|
|
57
59
|
if (REFUSED_STATUSES.has(httpStatus))
|
|
58
|
-
return { status: "refused",
|
|
60
|
+
return { status: "refused", ...failure };
|
|
59
61
|
if (httpStatus === 429)
|
|
60
|
-
return { status: name === RATE_LIMIT_EXCEEDED ? "unavailable" : "quota_exceeded",
|
|
62
|
+
return { status: name === RATE_LIMIT_EXCEEDED ? "unavailable" : "quota_exceeded", ...failure };
|
|
61
63
|
const isRetryable = httpStatus >= 500 || httpStatus === 408 || (httpStatus === 409 && name === CONCURRENT_IDEMPOTENT_REQUESTS);
|
|
62
|
-
return { status: isRetryable ? "unavailable" : "rejected",
|
|
64
|
+
return { status: isRetryable ? "unavailable" : "rejected", ...failure };
|
|
65
|
+
}
|
|
66
|
+
const DELAY_SECONDS = /^\d{1,10}$/;
|
|
67
|
+
/** The IMF-fixdate form of an HTTP date, the one senders must produce (RFC 9110 §5.6.7). */
|
|
68
|
+
const HTTP_DATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
|
69
|
+
/**
|
|
70
|
+
* Reads a `Retry-After` header (RFC 9110 §10.2.3): a whole number of seconds, or an HTTP date (IMF-fixdate) turned into the seconds
|
|
71
|
+
* from `nowMs` to it (0 for a date in the past). `null` when there is none or it is malformed.
|
|
72
|
+
*/
|
|
73
|
+
export function readRetryAfter(value, nowMs) {
|
|
74
|
+
const text = value?.trim() ?? "";
|
|
75
|
+
if (text === "")
|
|
76
|
+
return null;
|
|
77
|
+
if (DELAY_SECONDS.test(text))
|
|
78
|
+
return Number(text);
|
|
79
|
+
const at = HTTP_DATE.test(text) ? Date.parse(text) : Number.NaN;
|
|
80
|
+
if (Number.isNaN(at))
|
|
81
|
+
return null;
|
|
82
|
+
return Math.max(0, Math.ceil((at - nowMs) / 1000));
|
|
63
83
|
}
|
|
64
84
|
function readString(payload, key) {
|
|
65
85
|
if (typeof payload !== "object" || payload === null || !Object.hasOwn(payload, key))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resend.js","sourceRoot":"","sources":["../../src/providers/resend.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"resend.js","sourceRoot":"","sources":["../../src/providers/resend.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,10 +75,14 @@ export type DeliveryOutcome =
|
|
|
75
75
|
| {
|
|
76
76
|
readonly status: "uncertain";
|
|
77
77
|
}
|
|
78
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* The provider was unavailable; the claim is released for a later run. `retryAfterSeconds` is the wait the provider
|
|
80
|
+
* asked for, when it named one.
|
|
81
|
+
*/
|
|
79
82
|
| {
|
|
80
83
|
readonly status: "retry-later";
|
|
81
84
|
readonly httpStatus?: number;
|
|
85
|
+
readonly retryAfterSeconds?: number;
|
|
82
86
|
}
|
|
83
87
|
/**
|
|
84
88
|
* The provider refused the account or its quota is spent: the claim is released, its attempt given back. Stop
|
|
@@ -88,6 +92,7 @@ export type DeliveryOutcome =
|
|
|
88
92
|
readonly status: "halted";
|
|
89
93
|
readonly reason: HaltingErrorCode;
|
|
90
94
|
readonly httpStatus?: number;
|
|
95
|
+
readonly retryAfterSeconds?: number;
|
|
91
96
|
};
|
|
92
97
|
/**
|
|
93
98
|
* Sends `delivery.mail` unless the ledger already has an outcome (or a fresh claim) for its scope
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deliveries.d.ts","sourceRoot":"","sources":["../../src/server/deliveries.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,OAAO,EAA2C,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnH,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAQzG,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAEvD,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,CAAC;AAKpF,MAAM,WAAW,QAAQ;IACvB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,oFAAoF;AACpF,MAAM,MAAM,gBAAgB,GAAG,0BAA0B,GAAG,wBAAwB,CAAC;AAErF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG,qBAAqB,CAAC;AAErE,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAI9F;AAED,MAAM,MAAM,eAAe;AACzB,mDAAmD;AACjD;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AAClD,4FAA4F;GAC1F;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE;AAClG,qEAAqE;GACnE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE;AACpE,4DAA4D;GAC1D;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE;AAClC;;;GAGG;GACD;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE;AAClC
|
|
1
|
+
{"version":3,"file":"deliveries.d.ts","sourceRoot":"","sources":["../../src/server/deliveries.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,OAAO,EAA2C,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnH,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAQzG,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAEvD,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,CAAC;AAKpF,MAAM,WAAW,QAAQ;IACvB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,oFAAoF;AACpF,MAAM,MAAM,gBAAgB,GAAG,0BAA0B,GAAG,wBAAwB,CAAC;AAErF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG,qBAAqB,CAAC;AAErE,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAI9F;AAED,MAAM,MAAM,eAAe;AACzB,mDAAmD;AACjD;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AAClD,4FAA4F;GAC1F;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE;AAClG,qEAAqE;GACnE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE;AACpE,4DAA4D;GAC1D;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE;AAClC;;;GAGG;GACD;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE;AAClC;;;GAGG;GACD;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE;AACvG;;;GAGG;GACD;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExI;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,eAAe,CAAC,CA8BlI;AAED,wFAAwF;AACxF,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,GAAG,oBAAoB,CAAA;CAAE,GAChG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAChC;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC;AAErC;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgBxI;AAuBD,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD"}
|
|
@@ -50,13 +50,14 @@ export async function deliverOnce(ctx, delivery, options = {}) {
|
|
|
50
50
|
}
|
|
51
51
|
const providerStatus = result.httpStatus ?? null;
|
|
52
52
|
const httpStatus = result.httpStatus === undefined ? {} : { httpStatus: result.httpStatus };
|
|
53
|
+
const retryAfter = result.retryAfterSeconds === undefined ? {} : { retryAfterSeconds: result.retryAfterSeconds };
|
|
53
54
|
if (isHaltingCode(result.error)) {
|
|
54
55
|
await releaseDelivery(ctx, fence, { providerStatus, giveAttemptBack: true });
|
|
55
|
-
return { status: "halted", reason: result.error, ...httpStatus };
|
|
56
|
+
return { status: "halted", reason: result.error, ...httpStatus, ...retryAfter };
|
|
56
57
|
}
|
|
57
58
|
if (result.error === "mailing.unavailable" && (maxAttempts === null || attempt < maxAttempts)) {
|
|
58
59
|
await releaseDelivery(ctx, fence, { providerStatus, giveAttemptBack: false });
|
|
59
|
-
return { status: "retry-later", ...httpStatus };
|
|
60
|
+
return { status: "retry-later", ...httpStatus, ...retryAfter };
|
|
60
61
|
}
|
|
61
62
|
await closeDelivery(ctx, fence, { status: "rejected", providerMessageId: null, reason: result.error, providerStatus });
|
|
62
63
|
return { status: "rejected", reason: result.error, ...httpStatus };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deliveries.js","sourceRoot":"","sources":["../../src/server/deliveries.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,mGAAmG;AACnG,mGAAmG;AACnG,kGAAkG;AAClG,iGAAiG;AACjG,8FAA8F;AAC9F,8FAA8F;AAC9F,OAAO,EAAsB,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAY,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAA4C,MAAM,gBAAgB,CAAC;AACnH,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIlD,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,CAAC;AAEpF,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,KAAK,GAAG,yBAAyB,CAAC;AAgDxC,kHAAkH;AAClH,MAAM,UAAU,UAAU,CAAC,OAAwB,EAAE,gBAAyB;IAC5E,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,gBAAgB,IAAI,OAAO,CAAC,MAAM,KAAK,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC;IACxG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC;IACpF,OAAO,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;AACpG,CAAC;
|
|
1
|
+
{"version":3,"file":"deliveries.js","sourceRoot":"","sources":["../../src/server/deliveries.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,8 @@ export interface MailContext {
|
|
|
15
15
|
* `invalid_input` (fix the input), `rejected` (do not retry), `unavailable` (retry later with the
|
|
16
16
|
* same `idempotencyKey`), `suppressed` (a list mail to someone who unsubscribed; do not retry),
|
|
17
17
|
* `provider_refused` / `quota_exceeded` (the account cannot send: stop, fix or wait, then retry). A
|
|
18
|
-
* failure the provider answered with an HTTP status carries it as `httpStatus
|
|
18
|
+
* failure the provider answered with an HTTP status carries it as `httpStatus`, and the wait it asked for
|
|
19
|
+
* (`Retry-After`) as `retryAfterSeconds`.
|
|
19
20
|
* Throws only when the module is not enabled, or for list mail without `context.db`.
|
|
20
21
|
*/
|
|
21
22
|
export declare function sendMail(context: MailContext, mail: OutgoingMail, options?: SendMailOptions): Promise<SendMailResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-mail.d.ts","sourceRoot":"","sources":["../../src/server/send-mail.ts"],"names":[],"mappings":"AAKA,OAAO,EAAO,KAAK,GAAG,EAAM,KAAK,EAAE,EAAkB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAClG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAIL,KAAK,YAAY,EAEjB,KAAK,eAAe,EAEpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EAAwG,KAAK,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAGvJ;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC;CACzB;AAED
|
|
1
|
+
{"version":3,"file":"send-mail.d.ts","sourceRoot":"","sources":["../../src/server/send-mail.ts"],"names":[],"mappings":"AAKA,OAAO,EAAO,KAAK,GAAG,EAAM,KAAK,EAAE,EAAkB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAClG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAIL,KAAK,YAAY,EAEjB,KAAK,eAAe,EAEpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EAAwG,KAAK,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAGvJ;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CA6B/H;AAED,gHAAgH;AAChH,MAAM,MAAM,kBAAkB,GAC1B,CAAC,GAAG,CAAC,uBAAuB,CAAC,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,GACvE,CAAC,GAAG,CAAC,qBAAqB,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,uBAAuB,CAAA;CAAE,CAAC,CAAC;AAE/E,MAAM,MAAM,iBAAiB,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,kBAAkB,CAAC;AAEzE,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,oEAAoE;IACpE,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,kBAAuB,GAAG,iBAAiB,CAO1H"}
|
package/dist/server/send-mail.js
CHANGED
|
@@ -15,7 +15,8 @@ import { validateMail } from "./validate-mail.js";
|
|
|
15
15
|
* `invalid_input` (fix the input), `rejected` (do not retry), `unavailable` (retry later with the
|
|
16
16
|
* same `idempotencyKey`), `suppressed` (a list mail to someone who unsubscribed; do not retry),
|
|
17
17
|
* `provider_refused` / `quota_exceeded` (the account cannot send: stop, fix or wait, then retry). A
|
|
18
|
-
* failure the provider answered with an HTTP status carries it as `httpStatus
|
|
18
|
+
* failure the provider answered with an HTTP status carries it as `httpStatus`, and the wait it asked for
|
|
19
|
+
* (`Retry-After`) as `retryAfterSeconds`.
|
|
19
20
|
* Throws only when the module is not enabled, or for list mail without `context.db`.
|
|
20
21
|
*/
|
|
21
22
|
export async function sendMail(context, mail, options = {}) {
|
|
@@ -40,7 +41,11 @@ export async function sendMail(context, mail, options = {}) {
|
|
|
40
41
|
}
|
|
41
42
|
const code = FAILURE_CODES[outcome.status];
|
|
42
43
|
logFailure(provider, code, { status: outcome.httpStatus });
|
|
43
|
-
return
|
|
44
|
+
return {
|
|
45
|
+
...err(code),
|
|
46
|
+
...(outcome.httpStatus === undefined ? {} : { httpStatus: outcome.httpStatus }),
|
|
47
|
+
...(outcome.retryAfterSeconds === undefined ? {} : { retryAfterSeconds: outcome.retryAfterSeconds }),
|
|
48
|
+
};
|
|
44
49
|
}
|
|
45
50
|
/**
|
|
46
51
|
* Exactly what `sendMail` would hand the provider for `mail`: the sender, the reply-to, and for list mail the
|
|
@@ -133,16 +138,23 @@ async function callProvider(provider, message, timeoutMs) {
|
|
|
133
138
|
function readOutcome(outcome) {
|
|
134
139
|
if (typeof outcome !== "object" || outcome === null)
|
|
135
140
|
return { status: "unavailable" };
|
|
136
|
-
const { status, id, httpStatus } = outcome;
|
|
141
|
+
const { status, id, httpStatus, retryAfterSeconds } = outcome;
|
|
137
142
|
if (status === "sent") {
|
|
138
143
|
return typeof id === "string" && id !== "" ? { status, id } : { status: "unavailable" };
|
|
139
144
|
}
|
|
140
145
|
if (typeof status === "string" && Object.hasOwn(FAILURE_CODES, status)) {
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
return {
|
|
147
|
+
status: status,
|
|
148
|
+
...(isHttpStatus(httpStatus) ? { httpStatus } : {}),
|
|
149
|
+
...(isRetryAfter(retryAfterSeconds) ? { retryAfterSeconds } : {}),
|
|
150
|
+
};
|
|
143
151
|
}
|
|
144
152
|
return { status: "unavailable" };
|
|
145
153
|
}
|
|
154
|
+
/** A provider's `Retry-After` in whole seconds; anything else is dropped. */
|
|
155
|
+
function isRetryAfter(value) {
|
|
156
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
157
|
+
}
|
|
146
158
|
function isHttpStatus(value) {
|
|
147
159
|
return typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599;
|
|
148
160
|
}
|