sently 0.9.2 → 1.0.0
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/AGENTS.md +1 -1
- package/CHANGELOG.md +86 -0
- package/README.md +39 -34
- package/SECURITY.md +59 -0
- package/dist/adapters/bun.js +1 -1
- package/dist/adapters/cf.js +2 -2
- package/dist/adapters/cf.js.map +1 -1
- package/dist/adapters/deno.js +1 -1
- package/dist/adapters/node.js +1 -1
- package/dist/auth/oauth2.js +2 -2
- package/dist/auth/oauth2.js.map +1 -1
- package/dist/channel-result.d.ts +6 -0
- package/dist/channel-result.js +1 -0
- package/dist/{chunk-skqhj0rm.js → chunk-1ke3nmyg.js} +3 -3
- package/dist/{chunk-skqhj0rm.js.map → chunk-1ke3nmyg.js.map} +1 -1
- package/dist/{chunk-7gy2q9sh.js → chunk-5es0v1j7.js} +2 -2
- package/dist/{chunk-7gy2q9sh.js.map → chunk-5es0v1j7.js.map} +1 -1
- package/dist/{chunk-0qxws3kj.js → chunk-7wtfjx0d.js} +2 -2
- package/dist/{chunk-0qxws3kj.js.map → chunk-7wtfjx0d.js.map} +1 -1
- package/dist/chunk-8kpgbrba.js +4 -0
- package/dist/{chunk-93vqxxj2.js.map → chunk-8kpgbrba.js.map} +2 -2
- package/dist/{chunk-vjpds2pf.js → chunk-a057jzsy.js} +2 -2
- package/dist/{chunk-vjpds2pf.js.map → chunk-a057jzsy.js.map} +1 -1
- package/dist/{chunk-8njyga62.js → chunk-bhr7w3gg.js} +2 -2
- package/dist/{chunk-8njyga62.js.map → chunk-bhr7w3gg.js.map} +1 -1
- package/dist/chunk-cs1tvehn.js +4 -0
- package/dist/chunk-cs1tvehn.js.map +10 -0
- package/dist/{chunk-29z7fkzy.js → chunk-d5aw5vag.js} +3 -3
- package/dist/{chunk-29z7fkzy.js.map → chunk-d5aw5vag.js.map} +1 -1
- package/dist/chunk-f5x8vesr.js +4 -0
- package/dist/{chunk-b602dhck.js.map → chunk-f5x8vesr.js.map} +2 -2
- package/dist/chunk-fv0bemgf.js +4 -0
- package/dist/{chunk-yp311efr.js.map → chunk-fv0bemgf.js.map} +3 -3
- package/dist/{chunk-ywq6s10g.js → chunk-hnzmn4s4.js} +2 -2
- package/dist/{chunk-ywq6s10g.js.map → chunk-hnzmn4s4.js.map} +1 -1
- package/dist/chunk-htdg075t.js +4 -0
- package/dist/{chunk-qtkd5bak.js.map → chunk-htdg075t.js.map} +4 -3
- package/dist/{chunk-0gqy32pe.js → chunk-q2mahnb7.js} +3 -3
- package/dist/chunk-q2mahnb7.js.map +10 -0
- package/dist/chunk-sbgt4165.js +4 -0
- package/dist/chunk-sbgt4165.js.map +10 -0
- package/dist/{chunk-6npp3x3c.js → chunk-th4cwrpb.js} +2 -2
- package/dist/{chunk-6npp3x3c.js.map → chunk-th4cwrpb.js.map} +1 -1
- package/dist/{chunk-tamww15j.js → chunk-ttbwyxmh.js} +2 -2
- package/dist/{chunk-tamww15j.js.map → chunk-ttbwyxmh.js.map} +1 -1
- package/dist/chunk-w4c9kj70.js +13 -0
- package/dist/{chunk-n0qeyzqm.js.map → chunk-w4c9kj70.js.map} +2 -2
- package/dist/chunk-w9sabmb0.js +4 -0
- package/dist/chunk-w9sabmb0.js.map +10 -0
- package/dist/{chunk-8dty8c4p.js → chunk-yg3kj723.js} +3 -3
- package/dist/{chunk-8dty8c4p.js.map → chunk-yg3kj723.js.map} +2 -2
- package/dist/chunk-z1589fjk.js +4 -0
- package/dist/chunk-z1589fjk.js.map +10 -0
- package/dist/core/channel-result.d.ts +36 -0
- package/dist/core/channel-result.js +3 -0
- package/dist/core/channel-result.js.map +10 -0
- package/dist/core/decorated-transport.d.ts +30 -0
- package/dist/core/decorator-hooks.d.ts +17 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/provider-label.d.ts +7 -3
- package/dist/core/push-endpoint.d.ts +5 -0
- package/dist/core/push-types.d.ts +48 -14
- package/dist/core/sms-types.d.ts +12 -0
- package/dist/core/smtp.js +2 -2
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/whatsapp-types.d.ts +12 -0
- package/dist/detect.js +2 -2
- package/dist/detect.js.map +1 -1
- package/dist/dkim.js +2 -2
- package/dist/dkim.js.map +2 -2
- package/dist/idempotency.js +2 -2
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/mailer.js +2 -2
- package/dist/mailer.js.map +1 -1
- package/dist/observability/console.js +1 -1
- package/dist/plugins/react.js +2 -2
- package/dist/plugins/react.js.map +1 -1
- package/dist/plugins/template.js +1 -1
- package/dist/pool/pool.js +2 -2
- package/dist/pool/pool.js.map +1 -1
- package/dist/push.d.ts +1 -0
- package/dist/push.js +2 -2
- package/dist/push.js.map +3 -3
- package/dist/sms.d.ts +1 -0
- package/dist/sms.js +2 -2
- package/dist/sms.js.map +3 -3
- package/dist/smtp-mailer.js +2 -2
- package/dist/smtp-mailer.js.map +1 -1
- package/dist/transports/brevo.js +2 -2
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/cloudflare-email.js +2 -2
- package/dist/transports/cloudflare-email.js.map +2 -2
- package/dist/transports/fallback.d.ts +32 -6
- package/dist/transports/fallback.js +2 -2
- package/dist/transports/fallback.js.map +1 -1
- package/dist/transports/fcm.d.ts +55 -0
- package/dist/transports/fcm.js +4 -0
- package/dist/transports/fcm.js.map +10 -0
- package/dist/transports/loops.js +2 -2
- package/dist/transports/loops.js.map +1 -1
- package/dist/transports/mailersend.js +2 -2
- package/dist/transports/mailersend.js.map +2 -2
- package/dist/transports/mailgun.js +2 -2
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/mailpit.d.ts +166 -0
- package/dist/transports/mailpit.js +3 -0
- package/dist/transports/mailpit.js.map +10 -0
- package/dist/transports/mailtrap.js +2 -2
- package/dist/transports/mailtrap.js.map +2 -2
- package/dist/transports/msegat.js +2 -2
- package/dist/transports/msegat.js.map +1 -1
- package/dist/transports/plunk.js +2 -2
- package/dist/transports/plunk.js.map +1 -1
- package/dist/transports/postmark.js +2 -2
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.js +2 -2
- package/dist/transports/preview.js.map +1 -1
- package/dist/transports/resend.js +2 -2
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.d.ts +28 -9
- package/dist/transports/retry.js +2 -2
- package/dist/transports/retry.js.map +3 -3
- package/dist/transports/sendgrid.js +2 -2
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.js +2 -2
- package/dist/transports/ses.js.map +2 -2
- package/dist/transports/smtp.js +2 -2
- package/dist/transports/smtp.js.map +1 -1
- package/dist/transports/sndr.js +2 -2
- package/dist/transports/sndr.js.map +1 -1
- package/dist/transports/sparkpost.js +2 -2
- package/dist/transports/sparkpost.js.map +2 -2
- package/dist/transports/taqnyat-mail.js +2 -2
- package/dist/transports/taqnyat-mail.js.map +1 -1
- package/dist/transports/taqnyat-sms.js +2 -2
- package/dist/transports/taqnyat-sms.js.map +1 -1
- package/dist/transports/taqnyat-whatsapp.js +2 -2
- package/dist/transports/taqnyat-whatsapp.js.map +1 -1
- package/dist/transports/twilio-sms.js +2 -2
- package/dist/transports/twilio-sms.js.map +2 -2
- package/dist/transports/unifonic.d.ts +81 -0
- package/dist/transports/unifonic.js +3 -0
- package/dist/transports/unifonic.js.map +10 -0
- package/dist/transports/webpush.js +2 -2
- package/dist/transports/webpush.js.map +3 -3
- package/dist/transports/weighted-fallback.d.ts +11 -9
- package/dist/transports/weighted-fallback.js +2 -2
- package/dist/transports/weighted-fallback.js.map +3 -3
- package/dist/transports/whatsapp-cloud.js +2 -2
- package/dist/transports/whatsapp-cloud.js.map +1 -1
- package/dist/webhooks/brevo.js +2 -2
- package/dist/webhooks/brevo.js.map +1 -1
- package/dist/webhooks/mailgun.js +2 -2
- package/dist/webhooks/mailgun.js.map +1 -1
- package/dist/webhooks/postmark.js +2 -2
- package/dist/webhooks/postmark.js.map +1 -1
- package/dist/webhooks/resend.js +2 -2
- package/dist/webhooks/resend.js.map +1 -1
- package/dist/webhooks/sendgrid.js +2 -2
- package/dist/webhooks/sendgrid.js.map +1 -1
- package/dist/webhooks/ses.js +2 -2
- package/dist/webhooks/ses.js.map +1 -1
- package/dist/webhooks/sndr.js +2 -2
- package/dist/webhooks/sndr.js.map +1 -1
- package/dist/webhooks/timing-safe-equal.js +1 -1
- package/dist/webhooks/twilio-sms.d.ts +20 -0
- package/dist/webhooks/twilio-sms.js +3 -0
- package/dist/webhooks/twilio-sms.js.map +10 -0
- package/dist/webhooks/types.d.ts +37 -3
- package/dist/webhooks/types.js +3 -0
- package/dist/webhooks/types.js.map +9 -0
- package/dist/webhooks/unifonic.d.ts +13 -0
- package/dist/webhooks/unifonic.js +3 -0
- package/dist/webhooks/unifonic.js.map +10 -0
- package/dist/webhooks/whatsapp-cloud.d.ts +14 -0
- package/dist/webhooks/whatsapp-cloud.js +3 -0
- package/dist/webhooks/whatsapp-cloud.js.map +10 -0
- package/dist/webhooks.d.ts +10 -5
- package/dist/webhooks.js +11 -0
- package/dist/whatsapp.d.ts +1 -0
- package/dist/whatsapp.js +2 -2
- package/dist/whatsapp.js.map +3 -3
- package/package.json +39 -2
- package/site/content/docs/ai/llms-txt.mdx +47 -2
- package/site/content/docs/channels/email.mdx +3 -0
- package/site/content/docs/channels/hooks.mdx +6 -5
- package/site/content/docs/channels/index.mdx +5 -3
- package/site/content/docs/channels/push.mdx +31 -21
- package/site/content/docs/channels/sms.mdx +4 -1
- package/site/content/docs/decorators/fallback.mdx +45 -13
- package/site/content/docs/decorators/index.mdx +31 -13
- package/site/content/docs/decorators/preview.mdx +52 -9
- package/site/content/docs/decorators/retry.mdx +32 -9
- package/site/content/docs/decorators/weighted-fallback.mdx +27 -6
- package/site/content/docs/get-started/entrypoints.mdx +11 -8
- package/site/content/docs/get-started/index.mdx +8 -2
- package/site/content/docs/get-started/installation.mdx +3 -2
- package/site/content/docs/get-started/introduction.mdx +7 -3
- package/site/content/docs/get-started/meta.json +4 -1
- package/site/content/docs/get-started/migrate-nodemailer.mdx +132 -5
- package/site/content/docs/get-started/non-goals.mdx +57 -0
- package/site/content/docs/get-started/stability.mdx +69 -0
- package/site/content/docs/get-started/support-matrix.mdx +62 -0
- package/site/content/docs/guides/compare.mdx +87 -0
- package/site/content/docs/guides/failover.mdx +121 -0
- package/site/content/docs/guides/index.mdx +2 -0
- package/site/content/docs/guides/meta.json +2 -0
- package/site/content/docs/guides/security.mdx +38 -8
- package/site/content/docs/guides/webhooks.mdx +35 -20
- package/site/content/docs/index.mdx +78 -4
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/reference/channel-result.mdx +82 -0
- package/site/content/docs/reference/exports.mdx +9 -4
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/push-options.mdx +58 -3
- package/site/content/docs/reference/sms-options.mdx +18 -2
- package/site/content/docs/reference/webhook-events.mdx +38 -14
- package/site/content/docs/reference/whatsapp-options.mdx +19 -1
- package/site/content/docs/transports/fcm.mdx +84 -0
- package/site/content/docs/transports/index.mdx +11 -3
- package/site/content/docs/transports/mailpit.mdx +123 -0
- package/site/content/docs/transports/meta.json +4 -1
- package/site/content/docs/transports/smtp.mdx +61 -22
- package/site/content/docs/transports/unifonic.mdx +85 -0
- package/dist/chunk-0gqy32pe.js.map +0 -10
- package/dist/chunk-5915vbcj.js +0 -4
- package/dist/chunk-5915vbcj.js.map +0 -10
- package/dist/chunk-93vqxxj2.js +0 -4
- package/dist/chunk-b602dhck.js +0 -4
- package/dist/chunk-gp4fwrs8.js +0 -4
- package/dist/chunk-gp4fwrs8.js.map +0 -10
- package/dist/chunk-n0qeyzqm.js +0 -13
- package/dist/chunk-q8k3m6qe.js +0 -4
- package/dist/chunk-q8k3m6qe.js.map +0 -10
- package/dist/chunk-qtkd5bak.js +0 -4
- package/dist/chunk-yp311efr.js +0 -4
- package/site/content/docs/quick-start/email.mdx +0 -40
- package/site/content/docs/quick-start/index.mdx +0 -15
- package/site/content/docs/quick-start/meta.json +0 -5
- package/site/content/docs/quick-start/push.mdx +0 -36
- package/site/content/docs/quick-start/sms.mdx +0 -36
- package/site/content/docs/quick-start/whatsapp.mdx +0 -38
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"// src/core/mime.ts\nimport {\n assertSafeAddress,\n extractEmails,\n isValidEmail,\n parseAddresses,\n toMIMEHeader,\n} from \"./address.js\";\nimport { encodeBase64, encodeHeader, encodeUtf8 } from \"./base64.js\";\nimport type { Address, Attachment, DKIMConfig, Envelope, MailOptions } from \"./types.js\";\n\nfunction sanitizeHeaderValue(value: string): string {\n return value.replace(/\\r\\n?|\\n/g, \" \").trim();\n}\n\nfunction sanitizeAddress(addr: Address): Address {\n // Security check runs on the raw value FIRST and fails closed: any control\n // character (CR/LF/NUL/…) is rejected outright rather than stripped and\n // accepted. parseAddresses() already enforces this upstream; re-asserting\n // here keeps the function safe if it is ever called directly.\n assertSafeAddress(addr.address, \"address\");\n if (addr.name !== undefined) {\n assertSafeAddress(addr.name, \"display name\");\n }\n\n // Only ordinary surrounding whitespace is trimmed — the address identity is\n // never altered (no CR/LF-to-space repair).\n const address = addr.address.trim();\n if (!isValidEmail(address)) {\n throw new Error(`Invalid email address: ${JSON.stringify(addr.address)}`);\n }\n\n const sanitized: Address = { address };\n if (addr.name) {\n sanitized.name = sanitizeHeaderValue(addr.name);\n }\n return sanitized;\n}\n\n/** Result of building a complete MIME message. */\nexport interface MIMEBuildResult {\n /** Complete raw MIME message bytes ready to send. */\n raw: Uint8Array;\n /** SMTP envelope derived from From/To/Cc/Bcc. */\n envelope: Envelope;\n /** Message-ID assigned or supplied for the message. */\n messageId: string;\n /** Size of the raw MIME message in bytes. */\n size: number;\n}\n\nconst CRLF = \"\\r\\n\";\n\n/**\n * Build a complete MIME message as a Uint8Array ready for SMTP DATA.\n * When `dkim` is provided, signs the message and prepends the DKIM-Signature header.\n */\nexport async function buildMIME(options: MailOptions, dkim?: DKIMConfig): Promise<MIMEBuildResult> {\n const messageId = options.messageId ?? generateMessageId();\n const date = (options.date ?? new Date()).toUTCString();\n const fromAddrs = parseAddresses(options.from);\n const toAddrs = parseAddresses(options.to);\n const ccAddrs = options.cc ? parseAddresses(options.cc) : [];\n\n if (fromAddrs.length === 0) {\n throw new Error(\"Missing from address\");\n }\n if (toAddrs.length === 0) {\n throw new Error(\"Missing to address\");\n }\n\n const envelope: Envelope = {\n from: fromAddrs[0]?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n };\n\n const attachments = options.attachments ?? [];\n const inlineAttachments = attachments.filter((a) => a.inline || a.contentId);\n const regularAttachments = attachments.filter((a) => !a.inline && !a.contentId);\n\n let root = buildSimpleBody(options);\n\n if (inlineAttachments.length > 0) {\n const boundary = generateBoundary();\n root = {\n contentType: `multipart/related; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatNestedPart(buildSimpleBody(options)),\n ...inlineAttachments.map(formatAttachmentPart),\n ]),\n };\n }\n\n if (regularAttachments.length > 0) {\n const boundary = generateBoundary();\n root = {\n contentType: `multipart/mixed; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatNestedPart(root),\n ...regularAttachments.map(formatAttachmentPart),\n ]),\n };\n }\n\n const headers: string[] = [\n foldHeader(\"From\", fromAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")),\n foldHeader(\"To\", toAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")),\n ];\n\n if (ccAddrs.length > 0) {\n headers.push(foldHeader(\"Cc\", ccAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")));\n }\n\n if (options.replyTo) {\n headers.push(\n foldHeader(\n \"Reply-To\",\n parseAddresses(options.replyTo)\n .map((a) => toMIMEHeader(sanitizeAddress(a)))\n .join(\", \"),\n ),\n );\n }\n\n headers.push(\n foldHeader(\"Subject\", encodeHeader(sanitizeHeaderValue(options.subject))),\n foldHeader(\"Date\", date),\n foldHeader(\"Message-ID\", messageId),\n \"MIME-Version: 1.0\",\n );\n\n if (options.priority === \"high\") {\n headers.push(\"X-Priority: 1\", \"Importance: high\");\n } else if (options.priority === \"low\") {\n headers.push(\"X-Priority: 5\", \"Importance: low\");\n }\n\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n headers.push(foldHeader(sanitizeHeaderValue(key), sanitizeHeaderValue(value)));\n }\n }\n\n headers.push(`Content-Type: ${root.contentType}`);\n if (root.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${root.contentTransferEncoding}`);\n }\n\n const rawText = `${headers.join(CRLF)}${CRLF}${CRLF}${root.content}`;\n let raw = encodeUtf8(rawText);\n\n if (dkim) {\n const { signDKIM } = await import(\"../dkim.js\");\n const { header } = await signDKIM(raw, dkim);\n const signedText = `${header}${CRLF}${rawText}`;\n raw = encodeUtf8(signedText);\n }\n\n return { raw, envelope, messageId, size: raw.length };\n}\n\ninterface SimpleBody {\n contentType: string;\n contentTransferEncoding?: string;\n content: string;\n}\n\nfunction buildSimpleBody(options: MailOptions): SimpleBody {\n const hasText = Boolean(options.text);\n const hasHtml = Boolean(options.html);\n\n if (hasText && hasHtml) {\n const boundary = generateBoundary();\n return {\n contentType: `multipart/alternative; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatSimplePart({\n contentType: \"text/plain; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.text ?? \"\",\n }),\n formatSimplePart({\n contentType: \"text/html; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.html ?? \"\",\n }),\n ]),\n };\n }\n\n if (hasHtml) {\n return {\n contentType: \"text/html; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.html ?? \"\",\n };\n }\n\n return {\n contentType: \"text/plain; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.text ?? \"\",\n };\n}\n\nfunction formatSimplePart(part: SimpleBody): string {\n const headers = [`Content-Type: ${part.contentType}`];\n if (part.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${part.contentTransferEncoding}`);\n }\n return `${headers.join(CRLF)}${CRLF}${CRLF}${part.content}`;\n}\n\nfunction formatNestedPart(part: SimpleBody): string {\n const headers = [`Content-Type: ${part.contentType}`];\n if (part.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${part.contentTransferEncoding}`);\n }\n return `${headers.join(CRLF)}${CRLF}${CRLF}${part.content}`;\n}\n\nfunction formatAttachmentPart(attachment: Attachment): string {\n if (!attachment.content || typeof attachment.content === \"string\") {\n throw new Error(`Attachment \"${attachment.filename}\" requires Uint8Array content`);\n }\n\n const safeFilename = sanitizeHeaderValue(attachment.filename ?? \"\");\n const headers = [\n `Content-Type: ${attachment.contentType ?? \"application/octet-stream\"}`,\n \"Content-Transfer-Encoding: base64\",\n `Content-Disposition: ${attachment.inline ? \"inline\" : \"attachment\"}; filename=\"${safeFilename}\"`,\n ];\n\n if (attachment.contentId) {\n headers.push(`Content-ID: <${attachment.contentId}>`);\n }\n\n if (attachment.headers) {\n for (const [key, value] of Object.entries(attachment.headers)) {\n const safeKey = sanitizeHeaderValue(key);\n const safeValue = sanitizeHeaderValue(value);\n headers.push(`${safeKey}: ${safeValue}`);\n }\n }\n\n return `${headers.join(CRLF)}${CRLF}${CRLF}${encodeBase64(attachment.content)}`;\n}\n\nfunction assembleMultipart(boundary: string, parts: string[]): string {\n const segments = parts.map((part) => `--${boundary}${CRLF}${part}`);\n segments.push(`--${boundary}--`);\n return segments.join(CRLF);\n}\n\nfunction generateMessageId(): string {\n const random = crypto.getRandomValues(new Uint8Array(8));\n const hex = Array.from(random, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `<${Date.now()}.${hex}@sently>`;\n}\n\nfunction generateBoundary(): string {\n const random = crypto.getRandomValues(new Uint8Array(12));\n const hex = Array.from(random, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `----sently_${hex}`;\n}\n\nfunction foldHeader(name: string, value: string): string {\n const line = `${name}: ${value}`;\n if (line.length <= 76) {\n return line;\n }\n\n const chunks: string[] = [];\n let remaining = line;\n\n while (remaining.length > 76) {\n let breakAt = remaining.lastIndexOf(\" \", 76);\n if (breakAt <= name.length + 1) {\n breakAt = 76;\n }\n chunks.push(remaining.slice(0, breakAt));\n remaining = ` ${remaining.slice(breakAt).trimStart()}`;\n }\n chunks.push(remaining);\n\n return chunks.join(`${CRLF} `);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "88ABACC6FFC0A9F464756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/push-types.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * Shared push channel contracts — sibling types to email MailOptions/Transport/Hooks.\n *\n * Sently-first: this contract is the stable push surface. Provider transports\n * implement {@link PushTransport}. Web Push uses a browser subscription; FCM\n * uses a device registration token.\n */\nimport type { VerifyResult } from \"./types.js\";\n\n/** Browser push subscription as returned by the Push API. */\nexport interface PushSubscription {\n /** Push service endpoint URL. */\n endpoint: string;\n /** Client encryption keys for RFC 8291 payload encryption. */\n keys: {\n /** Base64url-encoded P-256 ECDH public key. */\n p256dh: string;\n /** Base64url-encoded authentication secret. */\n auth: string;\n };\n}\n\n/** Shared notification fields for Web Push and FCM. */\ninterface PushNotificationFields {\n /** Notification title. */\n title: string;\n /** Notification body text. */\n body: string;\n /** Arbitrary application data attached to the notification. */\n data?: Record<string, unknown>;\n /** Notification icon URL (Web Push / some FCM platforms). */\n icon?: string;\n /** Time-to-live in seconds for the push message. */\n ttl?: number;\n /** Client-supplied message identifier when known. */\n messageId?: string;\n}\n\n/** Options for sending a Web Push notification (VAPID / browser subscription). */\nexport interface WebPushOptions extends PushNotificationFields {\n /** Target browser push subscription. */\n subscription: PushSubscription;\n}\n\n/**\n * Options for sending via Firebase Cloud Messaging (device token).\n * Data values are stringified by the FCM transport when needed.\n */\nexport interface FcmPushOptions extends PushNotificationFields {\n /** FCM registration token for the target device. */\n token: string;\n /** Optional image URL for the notification. */\n image?: string;\n}\n\n/** Options for sending a push notification (Web Push or FCM). */\nexport type PushOptions = WebPushOptions | FcmPushOptions;\n\n/** True when options target FCM (device token) rather than Web Push. */\nexport function isFcmPushOptions(options: PushOptions): options is FcmPushOptions {\n return \"token\" in options && typeof (options as FcmPushOptions).token === \"string\";\n}\n\n/** True when options target Web Push (browser subscription). */\nexport function isWebPushOptions(options: PushOptions): options is WebPushOptions {\n return (\n \"subscription\" in options &&\n typeof (options as WebPushOptions).subscription?.endpoint === \"string\"\n );\n}\n\n/** Result returned after a push notification is accepted by the push service. */\nexport interface PushSendResult {\n /** Client-generated or assigned message identifier. */\n messageId: string;\n /** Delivery status string (e.g. `\"accepted\"`). */\n status: string;\n /** Human-readable or raw status text (often the HTTP status). */\n response: string;\n /** Transport or provider identifier (e.g. `\"webpush\"`, `\"fcm\"`). */\n provider?: string;\n /**\n * Zero-based index of the transport that handled the send in a fallback chain.\n * Set by {@link FallbackTransport}.\n */\n providerIndex?: number;\n}\n\n/** Pluggable push delivery backend (Web Push, FCM, …). */\nexport interface PushTransport {\n /**\n * Stable provider identifier for observability (e.g. `\"webpush\"`, `\"fcm\"`).\n * Prefer this over constructor-name inference in hooks.\n */\n readonly provider?: string;\n /** Send a push notification through this transport. */\n send(options: PushOptions): Promise<PushSendResult>;\n /** Test connectivity and credentials without sending. */\n verify?(): Promise<VerifyResult>;\n /** Release resources held by the transport. */\n close?(): Promise<void>;\n}\n\n/**\n * Context passed to push lifecycle hooks.\n * Uses a redacted subscription endpoint or device-token fingerprint (no natural `to`).\n */\nexport interface PushHookContext {\n /** Message-ID when known (from options or send result). */\n messageId?: string;\n /**\n * Redacted push target for observability: `origin/#<short-hash>` (Web Push)\n * or `fcm:#<short-hash>` (FCM device token).\n * Never the full endpoint URL or raw device token.\n */\n endpoint: string;\n /** Transport or provider identifier (e.g. `\"webpush\"`, `\"fcm\"`). */\n provider: string;\n}\n\n/** Optional lifecycle hooks for metrics, tracing, and observability on every push send. */\nexport interface PushHooks {\n /** Fired before the transport sends the notification. */\n onSend?: (ctx: PushHookContext) => void | Promise<void>;\n /**\n * Fired after a successful send.\n * @param durationMs — elapsed milliseconds from send start to success (optional third argument).\n */\n onSuccess?: (\n ctx: PushHookContext,\n result: PushSendResult,\n durationMs?: number,\n ) => void | Promise<void>;\n /**\n * Fired when a send throws (error is re-thrown after the hook runs).\n * @param durationMs — elapsed milliseconds from send start to failure (optional third argument).\n */\n onError?: (ctx: PushHookContext, error: unknown, durationMs?: number) => void | Promise<void>;\n /** Fired before each retry attempt (requires {@link RetryTransport}). */\n onRetry?: (ctx: PushHookContext, attempt: number, error: unknown) => void | Promise<void>;\n /**\n * Fired when {@link FallbackTransport} fails over to the next provider.\n * Requires a fallback (or weighted fallback) transport in the push stack.\n */\n onFallback?: (\n ctx: PushHookContext,\n failedProvider: string,\n nextProvider: string,\n error: unknown,\n ) => void | Promise<void>;\n}\n\n/**\n * A push plugin transforms {@link PushOptions} before the transport sends.\n * Plugins run sequentially. Each receives the output of the previous.\n * Return a new options object — do not mutate the input.\n */\nexport type PushPlugin =\n | ((options: PushOptions) => PushOptions)\n | ((options: PushOptions) => Promise<PushOptions>);\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "AA4DO,SAAS,CAAgB,CAAC,EAAiD,CAChF,MAAO,UAAW,GAAW,OAAQ,EAA2B,QAAU,SAIrE,SAAS,CAAgB,CAAC,EAAiD,CAChF,MACE,iBAAkB,GAClB,OAAQ,EAA2B,cAAc,WAAa",
|
|
8
|
+
"debugId": "F4A2DC6CC43BC46D64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Cross-channel send-result mapping — normalize email / SMS / WhatsApp / push
|
|
4
|
+
* results into a shared `{ messageId, provider, accepted }` shape for adapters.
|
|
5
|
+
*/
|
|
6
|
+
import type { PushSendResult } from "./push-types.js";
|
|
7
|
+
import type { SmsSendResult } from "./sms-types.js";
|
|
8
|
+
import type { SendResult } from "./types.js";
|
|
9
|
+
import type { WhatsAppSendResult } from "./whatsapp-types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Stable send outcome shared across email, SMS, WhatsApp, and push.
|
|
12
|
+
* Prefer this in multi-channel adapter layers; channel-specific fields stay on
|
|
13
|
+
* the original result types.
|
|
14
|
+
*/
|
|
15
|
+
export interface ChannelSendResult {
|
|
16
|
+
/** Provider-assigned or client message identifier. */
|
|
17
|
+
messageId: string;
|
|
18
|
+
/** Transport or provider identifier when known. */
|
|
19
|
+
provider?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the provider accepted the message for delivery.
|
|
22
|
+
* Does not guarantee end-user delivery (use webhooks for that).
|
|
23
|
+
*/
|
|
24
|
+
accepted: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Any channel send result that {@link toChannelSendResult} can normalize. */
|
|
27
|
+
export type AnySendResult = SendResult | SmsSendResult | WhatsAppSendResult | PushSendResult;
|
|
28
|
+
/**
|
|
29
|
+
* Map a channel-specific send result to {@link ChannelSendResult}.
|
|
30
|
+
*
|
|
31
|
+
* | Channel result | `accepted` mapping |
|
|
32
|
+
* | --- | --- |
|
|
33
|
+
* | Email {@link SendResult} | `accepted.length > 0` and `rejected.length === 0` |
|
|
34
|
+
* | SMS / WhatsApp / Push | `status` in accepted set (case-insensitive), else `false` |
|
|
35
|
+
*/
|
|
36
|
+
export declare function toChannelSendResult(result: AnySendResult): ChannelSendResult;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import"../chunk-th4cwrpb.js";var x=new Set(["accepted","queued","sending","sent","delivered","read","success"]);function B(b){if(y(b)){let w={messageId:b.messageId,accepted:b.accepted.length>0&&b.rejected.length===0};if(b.provider!==void 0)w.provider=b.provider;return w}let q={messageId:b.messageId,accepted:z(b.status)};if(b.provider!==void 0)q.provider=b.provider;return q}function y(b){return Array.isArray(b.accepted)&&Array.isArray(b.rejected)}function z(b){return x.has(b.trim().toLowerCase())}export{B as toChannelSendResult};
|
|
2
|
+
|
|
3
|
+
//# debugId=D355389C6F39068164756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/channel-result.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * Cross-channel send-result mapping — normalize email / SMS / WhatsApp / push\n * results into a shared `{ messageId, provider, accepted }` shape for adapters.\n */\nimport type { PushSendResult } from \"./push-types.js\";\nimport type { SmsSendResult } from \"./sms-types.js\";\nimport type { SendResult } from \"./types.js\";\nimport type { WhatsAppSendResult } from \"./whatsapp-types.js\";\n\n/**\n * Stable send outcome shared across email, SMS, WhatsApp, and push.\n * Prefer this in multi-channel adapter layers; channel-specific fields stay on\n * the original result types.\n */\nexport interface ChannelSendResult {\n /** Provider-assigned or client message identifier. */\n messageId: string;\n /** Transport or provider identifier when known. */\n provider?: string;\n /**\n * Whether the provider accepted the message for delivery.\n * Does not guarantee end-user delivery (use webhooks for that).\n */\n accepted: boolean;\n}\n\n/** Any channel send result that {@link toChannelSendResult} can normalize. */\nexport type AnySendResult = SendResult | SmsSendResult | WhatsAppSendResult | PushSendResult;\n\n/**\n * Status strings treated as provider-accepted for SMS / WhatsApp / push.\n * Twilio often returns `\"queued\"`; Unifonic may return `\"Sent\"`.\n */\nconst ACCEPTED_STATUSES = new Set([\n \"accepted\",\n \"queued\",\n \"sending\",\n \"sent\",\n \"delivered\",\n \"read\",\n \"success\",\n]);\n\n/**\n * Map a channel-specific send result to {@link ChannelSendResult}.\n *\n * | Channel result | `accepted` mapping |\n * | --- | --- |\n * | Email {@link SendResult} | `accepted.length > 0` and `rejected.length === 0` |\n * | SMS / WhatsApp / Push | `status` in accepted set (case-insensitive), else `false` |\n */\nexport function toChannelSendResult(result: AnySendResult): ChannelSendResult {\n if (isEmailSendResult(result)) {\n const out: ChannelSendResult = {\n messageId: result.messageId,\n accepted: result.accepted.length > 0 && result.rejected.length === 0,\n };\n if (result.provider !== undefined) {\n out.provider = result.provider;\n }\n return out;\n }\n\n const out: ChannelSendResult = {\n messageId: result.messageId,\n accepted: isAcceptedStatus(result.status),\n };\n if (result.provider !== undefined) {\n out.provider = result.provider;\n }\n return out;\n}\n\nfunction isEmailSendResult(result: AnySendResult): result is SendResult {\n return (\n Array.isArray((result as SendResult).accepted) && Array.isArray((result as SendResult).rejected)\n );\n}\n\nfunction isAcceptedStatus(status: string): boolean {\n return ACCEPTED_STATUSES.has(status.trim().toLowerCase());\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "6BAkCA,IAAM,EAAoB,IAAI,IAAI,CAChC,WACA,SACA,UACA,OACA,YACA,OACA,SACF,CAAC,EAUM,SAAS,CAAmB,CAAC,EAA0C,CAC5E,GAAI,EAAkB,CAAM,EAAG,CAC7B,IAAM,EAAyB,CAC7B,UAAW,EAAO,UAClB,SAAU,EAAO,SAAS,OAAS,GAAK,EAAO,SAAS,SAAW,CACrE,EACA,GAAI,EAAO,WAAa,OACtB,EAAI,SAAW,EAAO,SAExB,OAAO,EAGT,IAAM,EAAyB,CAC7B,UAAW,EAAO,UAClB,SAAU,EAAiB,EAAO,MAAM,CAC1C,EACA,GAAI,EAAO,WAAa,OACtB,EAAI,SAAW,EAAO,SAExB,OAAO,EAGT,SAAS,CAAiB,CAAC,EAA6C,CACtE,OACE,MAAM,QAAS,EAAsB,QAAQ,GAAK,MAAM,QAAS,EAAsB,QAAQ,EAInG,SAAS,CAAgB,CAAC,EAAyB,CACjD,OAAO,EAAkB,IAAI,EAAO,KAAK,EAAE,YAAY,CAAC",
|
|
8
|
+
"debugId": "D355389C6F39068164756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Minimal transport surface for cross-channel retry / fallback decorators.
|
|
4
|
+
*/
|
|
5
|
+
import type { VerifyResult } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Structural transport contract used by {@link RetryTransport},
|
|
8
|
+
* {@link FallbackTransport}, and {@link WeightedFallbackTransport}.
|
|
9
|
+
* Compatible with email {@link Transport}, {@link SmsTransport},
|
|
10
|
+
* {@link WhatsAppTransport}, and {@link PushTransport}.
|
|
11
|
+
*/
|
|
12
|
+
export interface DecoratedTransport<TOptions, TResult> {
|
|
13
|
+
/** Stable provider identifier for observability when set. */
|
|
14
|
+
readonly provider?: string;
|
|
15
|
+
/** Send through this transport. */
|
|
16
|
+
send(options: TOptions): Promise<TResult>;
|
|
17
|
+
/** Optional connectivity check. */
|
|
18
|
+
verify?(): Promise<VerifyResult>;
|
|
19
|
+
/** Optional resource cleanup. */
|
|
20
|
+
close?(): Promise<void>;
|
|
21
|
+
/** Optional batch send (email transports). */
|
|
22
|
+
sendBatch?(messages: TOptions[]): Promise<TResult[]>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Result fields added by fallback decorators when a chain member succeeds.
|
|
26
|
+
*/
|
|
27
|
+
export type FallbackAugmentedResult<TResult> = TResult & {
|
|
28
|
+
provider: string;
|
|
29
|
+
providerIndex: number;
|
|
30
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Duck-typed wiring for RetryTransport / FallbackTransport sender hooks.
|
|
4
|
+
* Kept separate so channel senders do not import the decorator modules.
|
|
5
|
+
*/
|
|
6
|
+
/** Transport that accepts per-send retry callbacks. */
|
|
7
|
+
export interface RetryHookTransport {
|
|
8
|
+
setMailerOnRetry(callback: ((attempt: number, error: unknown) => void) | undefined): void;
|
|
9
|
+
}
|
|
10
|
+
/** Transport that accepts per-send fallback callbacks. */
|
|
11
|
+
export interface FallbackHookTransport {
|
|
12
|
+
setMailerOnFallback(callback: ((failedProvider: string, nextProvider: string, error: unknown) => void) | undefined): void;
|
|
13
|
+
}
|
|
14
|
+
/** True when the transport exposes {@link RetryHookTransport.setMailerOnRetry}. */
|
|
15
|
+
export declare function isRetryHookTransport(transport: unknown): transport is RetryHookTransport;
|
|
16
|
+
/** True when the transport exposes {@link FallbackHookTransport.setMailerOnFallback}. */
|
|
17
|
+
export declare function isFallbackHookTransport(transport: unknown): transport is FallbackHookTransport;
|
package/dist/core/errors.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{ja as a,ka as b,la as c}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";export{c as smtpCodeToSentlyCode,b as httpStatusToSentlyCode,a as SentlyError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=977FBDF71FF6E17564756E2164756E21
|
package/dist/core/errors.js.map
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { Transport } from "./types.js";
|
|
2
1
|
/**
|
|
3
2
|
* Resolve a stable provider label for observability and diagnostics.
|
|
4
|
-
* Prefers
|
|
3
|
+
* Prefers `transport.provider` when set; falls back to constructor name.
|
|
5
4
|
*/
|
|
6
|
-
export declare function getProviderLabel(transport:
|
|
5
|
+
export declare function getProviderLabel(transport: {
|
|
6
|
+
readonly provider?: string;
|
|
7
|
+
readonly constructor: {
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
}): string;
|
|
@@ -27,3 +27,8 @@ export declare function assertSafePushEndpoint(endpoint: string, extraHosts?: re
|
|
|
27
27
|
* Returns `origin/#<8-byte-hash>` so logs never store the delivery token path.
|
|
28
28
|
*/
|
|
29
29
|
export declare function redactPushEndpoint(endpoint: string): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Redact an FCM device token for hook/observability context.
|
|
32
|
+
* Returns `fcm:#<8-byte-hash>` so logs never store the raw registration token.
|
|
33
|
+
*/
|
|
34
|
+
export declare function redactFcmToken(token: string): Promise<string>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module
|
|
3
|
-
* Shared
|
|
3
|
+
* Shared push channel contracts — sibling types to email MailOptions/Transport/Hooks.
|
|
4
4
|
*
|
|
5
5
|
* Sently-first: this contract is the stable push surface. Provider transports
|
|
6
|
-
* implement {@link PushTransport}.
|
|
6
|
+
* implement {@link PushTransport}. Web Push uses a browser subscription; FCM
|
|
7
|
+
* uses a device registration token.
|
|
7
8
|
*/
|
|
8
9
|
import type { VerifyResult } from "./types.js";
|
|
9
10
|
/** Browser push subscription as returned by the Push API. */
|
|
@@ -18,23 +19,42 @@ export interface PushSubscription {
|
|
|
18
19
|
auth: string;
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/** Target browser push subscription. */
|
|
24
|
-
subscription: PushSubscription;
|
|
22
|
+
/** Shared notification fields for Web Push and FCM. */
|
|
23
|
+
interface PushNotificationFields {
|
|
25
24
|
/** Notification title. */
|
|
26
25
|
title: string;
|
|
27
26
|
/** Notification body text. */
|
|
28
27
|
body: string;
|
|
29
28
|
/** Arbitrary application data attached to the notification. */
|
|
30
29
|
data?: Record<string, unknown>;
|
|
31
|
-
/** Notification icon URL. */
|
|
30
|
+
/** Notification icon URL (Web Push / some FCM platforms). */
|
|
32
31
|
icon?: string;
|
|
33
32
|
/** Time-to-live in seconds for the push message. */
|
|
34
33
|
ttl?: number;
|
|
35
34
|
/** Client-supplied message identifier when known. */
|
|
36
35
|
messageId?: string;
|
|
37
36
|
}
|
|
37
|
+
/** Options for sending a Web Push notification (VAPID / browser subscription). */
|
|
38
|
+
export interface WebPushOptions extends PushNotificationFields {
|
|
39
|
+
/** Target browser push subscription. */
|
|
40
|
+
subscription: PushSubscription;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Options for sending via Firebase Cloud Messaging (device token).
|
|
44
|
+
* Data values are stringified by the FCM transport when needed.
|
|
45
|
+
*/
|
|
46
|
+
export interface FcmPushOptions extends PushNotificationFields {
|
|
47
|
+
/** FCM registration token for the target device. */
|
|
48
|
+
token: string;
|
|
49
|
+
/** Optional image URL for the notification. */
|
|
50
|
+
image?: string;
|
|
51
|
+
}
|
|
52
|
+
/** Options for sending a push notification (Web Push or FCM). */
|
|
53
|
+
export type PushOptions = WebPushOptions | FcmPushOptions;
|
|
54
|
+
/** True when options target FCM (device token) rather than Web Push. */
|
|
55
|
+
export declare function isFcmPushOptions(options: PushOptions): options is FcmPushOptions;
|
|
56
|
+
/** True when options target Web Push (browser subscription). */
|
|
57
|
+
export declare function isWebPushOptions(options: PushOptions): options is WebPushOptions;
|
|
38
58
|
/** Result returned after a push notification is accepted by the push service. */
|
|
39
59
|
export interface PushSendResult {
|
|
40
60
|
/** Client-generated or assigned message identifier. */
|
|
@@ -43,13 +63,18 @@ export interface PushSendResult {
|
|
|
43
63
|
status: string;
|
|
44
64
|
/** Human-readable or raw status text (often the HTTP status). */
|
|
45
65
|
response: string;
|
|
46
|
-
/** Transport or provider identifier (e.g. `"webpush"`). */
|
|
66
|
+
/** Transport or provider identifier (e.g. `"webpush"`, `"fcm"`). */
|
|
47
67
|
provider?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Zero-based index of the transport that handled the send in a fallback chain.
|
|
70
|
+
* Set by {@link FallbackTransport}.
|
|
71
|
+
*/
|
|
72
|
+
providerIndex?: number;
|
|
48
73
|
}
|
|
49
|
-
/** Pluggable Web Push
|
|
74
|
+
/** Pluggable push delivery backend (Web Push, FCM, …). */
|
|
50
75
|
export interface PushTransport {
|
|
51
76
|
/**
|
|
52
|
-
* Stable provider identifier for observability (e.g. `"webpush"`).
|
|
77
|
+
* Stable provider identifier for observability (e.g. `"webpush"`, `"fcm"`).
|
|
53
78
|
* Prefer this over constructor-name inference in hooks.
|
|
54
79
|
*/
|
|
55
80
|
readonly provider?: string;
|
|
@@ -62,17 +87,18 @@ export interface PushTransport {
|
|
|
62
87
|
}
|
|
63
88
|
/**
|
|
64
89
|
* Context passed to push lifecycle hooks.
|
|
65
|
-
* Uses a redacted subscription endpoint
|
|
90
|
+
* Uses a redacted subscription endpoint or device-token fingerprint (no natural `to`).
|
|
66
91
|
*/
|
|
67
92
|
export interface PushHookContext {
|
|
68
93
|
/** Message-ID when known (from options or send result). */
|
|
69
94
|
messageId?: string;
|
|
70
95
|
/**
|
|
71
|
-
* Redacted push
|
|
72
|
-
*
|
|
96
|
+
* Redacted push target for observability: `origin/#<short-hash>` (Web Push)
|
|
97
|
+
* or `fcm:#<short-hash>` (FCM device token).
|
|
98
|
+
* Never the full endpoint URL or raw device token.
|
|
73
99
|
*/
|
|
74
100
|
endpoint: string;
|
|
75
|
-
/** Transport or provider identifier (e.g. `"webpush"`). */
|
|
101
|
+
/** Transport or provider identifier (e.g. `"webpush"`, `"fcm"`). */
|
|
76
102
|
provider: string;
|
|
77
103
|
}
|
|
78
104
|
/** Optional lifecycle hooks for metrics, tracing, and observability on every push send. */
|
|
@@ -89,6 +115,13 @@ export interface PushHooks {
|
|
|
89
115
|
* @param durationMs — elapsed milliseconds from send start to failure (optional third argument).
|
|
90
116
|
*/
|
|
91
117
|
onError?: (ctx: PushHookContext, error: unknown, durationMs?: number) => void | Promise<void>;
|
|
118
|
+
/** Fired before each retry attempt (requires {@link RetryTransport}). */
|
|
119
|
+
onRetry?: (ctx: PushHookContext, attempt: number, error: unknown) => void | Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Fired when {@link FallbackTransport} fails over to the next provider.
|
|
122
|
+
* Requires a fallback (or weighted fallback) transport in the push stack.
|
|
123
|
+
*/
|
|
124
|
+
onFallback?: (ctx: PushHookContext, failedProvider: string, nextProvider: string, error: unknown) => void | Promise<void>;
|
|
92
125
|
}
|
|
93
126
|
/**
|
|
94
127
|
* A push plugin transforms {@link PushOptions} before the transport sends.
|
|
@@ -96,3 +129,4 @@ export interface PushHooks {
|
|
|
96
129
|
* Return a new options object — do not mutate the input.
|
|
97
130
|
*/
|
|
98
131
|
export type PushPlugin = ((options: PushOptions) => PushOptions) | ((options: PushOptions) => Promise<PushOptions>);
|
|
132
|
+
export {};
|
package/dist/core/sms-types.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export interface SmsSendResult {
|
|
|
29
29
|
response: string;
|
|
30
30
|
/** Transport or provider identifier (e.g. `"twilio-sms"`). */
|
|
31
31
|
provider?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Zero-based index of the transport that handled the send in a fallback chain.
|
|
34
|
+
* Set by {@link FallbackTransport}.
|
|
35
|
+
*/
|
|
36
|
+
providerIndex?: number;
|
|
32
37
|
}
|
|
33
38
|
/** Pluggable SMS delivery backend. */
|
|
34
39
|
export interface SmsTransport {
|
|
@@ -67,6 +72,13 @@ export interface SmsHooks {
|
|
|
67
72
|
* @param durationMs — elapsed milliseconds from send start to failure (optional third argument).
|
|
68
73
|
*/
|
|
69
74
|
onError?: (ctx: SmsHookContext, error: unknown, durationMs?: number) => void | Promise<void>;
|
|
75
|
+
/** Fired before each retry attempt (requires {@link RetryTransport}). */
|
|
76
|
+
onRetry?: (ctx: SmsHookContext, attempt: number, error: unknown) => void | Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Fired when {@link FallbackTransport} fails over to the next provider.
|
|
79
|
+
* Requires a fallback (or weighted fallback) transport in the SMS stack.
|
|
80
|
+
*/
|
|
81
|
+
onFallback?: (ctx: SmsHookContext, failedProvider: string, nextProvider: string, error: unknown) => void | Promise<void>;
|
|
70
82
|
}
|
|
71
83
|
/**
|
|
72
84
|
* An SMS plugin transforms {@link SmsOptions} before the transport sends.
|
package/dist/core/smtp.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{A as
|
|
1
|
+
import{A as d,B as e,C as f,D as g,E as h,F as i,G as j,H as k,I as l,x as a,y as b,z as c}from"../chunk-w4c9kj70.js";import"../chunk-hnzmn4s4.js";import"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";export{f as selectAuthMethod,d as parseResponse,g as parseEHLO,l as encodeLine,c as encodeCommand,j as encodeAuthLoginUser,i as encodeAuthLoginPass,k as encodeAuthCramResponse,a as computeCRAMMD5,h as assertResponse,e as accumulateResponse,b as SMTPError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=A8BCD227899899A064756E2164756E21
|
package/dist/core/smtp.js.map
CHANGED
|
@@ -69,6 +69,11 @@ export interface WhatsAppSendResult {
|
|
|
69
69
|
response: string;
|
|
70
70
|
/** Transport or provider identifier (e.g. `"whatsapp-cloud"`). */
|
|
71
71
|
provider?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Zero-based index of the transport that handled the send in a fallback chain.
|
|
74
|
+
* Set by {@link FallbackTransport}.
|
|
75
|
+
*/
|
|
76
|
+
providerIndex?: number;
|
|
72
77
|
}
|
|
73
78
|
/** Pluggable WhatsApp delivery backend. */
|
|
74
79
|
export interface WhatsAppTransport {
|
|
@@ -107,6 +112,13 @@ export interface WhatsAppHooks {
|
|
|
107
112
|
* @param durationMs — elapsed milliseconds from send start to failure (optional third argument).
|
|
108
113
|
*/
|
|
109
114
|
onError?: (ctx: WhatsAppHookContext, error: unknown, durationMs?: number) => void | Promise<void>;
|
|
115
|
+
/** Fired before each retry attempt (requires {@link RetryTransport}). */
|
|
116
|
+
onRetry?: (ctx: WhatsAppHookContext, attempt: number, error: unknown) => void | Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Fired when {@link FallbackTransport} fails over to the next provider.
|
|
119
|
+
* Requires a fallback (or weighted fallback) transport in the WhatsApp stack.
|
|
120
|
+
*/
|
|
121
|
+
onFallback?: (ctx: WhatsAppHookContext, failedProvider: string, nextProvider: string, error: unknown) => void | Promise<void>;
|
|
110
122
|
}
|
|
111
123
|
/**
|
|
112
124
|
* A WhatsApp plugin transforms {@link WhatsAppOptions} before the transport sends.
|
package/dist/detect.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{L as a,M as b}from"./chunk-d5aw5vag.js";import"./chunk-th4cwrpb.js";export{a as detectRuntime,b as createDefaultAdapter};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=BEB4FD34279DF97564756E2164756E21
|
package/dist/detect.js.map
CHANGED
package/dist/dkim.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{da as G,ia as E}from"./chunk-hnzmn4s4.js";import"./chunk-th4cwrpb.js";var j=`\r
|
|
2
2
|
`,C="from:to:subject:date:message-id:mime-version:content-type";function _(M,I){let x=F(M),K=[];for(let S of I){let Y=S.toLowerCase().trim(),z=x.get(Y);if(!z)continue;for(let D of z){let w=D.replace(/\r?\n/g,"").replace(/\s+/g," ").trim();K.push(`${Y}:${w}`)}}return K.length>0?`${K.join(j)}${j}`:""}function $(M){let x=M.replace(/\r\n/g,`
|
|
3
3
|
`).replace(/\r/g,`
|
|
4
4
|
`).split(`
|
|
@@ -6,4 +6,4 @@ import{Y as G,ba as E}from"./chunk-ywq6s10g.js";import"./chunk-6npp3x3c.js";var
|
|
|
6
6
|
\r
|
|
7
7
|
`),w=D>=0?z.slice(0,D):z,P=D>=0?z.slice(D+4):"",T=$(P),O=await k(E(T)),Q=x==="ed25519-sha256"?"ed25519-sha256":"rsa-sha256",X=Y.join(":"),Z=Math.floor(Date.now()/1000),W=["v=1",`a=${Q}`,"c=relaxed/relaxed",`d=${I.domainName}`,`s=${I.keySelector}`,`h=${X}`,`bh=${O}`,"b=",`t=${Z}`].join("; "),b="dkim-signature",H=`${w}${j}dkim-signature:${W}`,p=_(H,[...Y,"dkim-signature"]),A=await v(I.privateKey,x),R=E(p),U=await L(A,R,x),V=G(U).replace(/\r\n/g,"");return{header:`DKIM-Signature: v=1; a=${Q}; c=relaxed/relaxed; d=${I.domainName}; s=${I.keySelector}; h=${X}; bh=${O}; b=${V}; t=${Z}`}}function F(M){let I=new Map,x=M.split(/\r?\n/).filter((z)=>z.length>0),K="",S="",Y=()=>{if(!K)return;let z=K.toLowerCase(),D=I.get(z)??[];D.push(S),I.set(z,D),K="",S=""};for(let z of x){if(/^[ \t]/.test(z)&&K){S+=` ${z.trim()}`;continue}Y();let D=z.indexOf(":");if(D>0)K=z.slice(0,D).trim(),S=z.slice(D+1).trim()}return Y(),I}function B(M){let I=M.replace(/-----BEGIN[^-]+-----/g,"").replace(/-----END[^-]+-----/g,"").replace(/\s/g,""),x=atob(I),K=new Uint8Array(x.length);for(let S=0;S<x.length;S++)K[S]=x.charCodeAt(S);return K}function J(M){return M.buffer.slice(M.byteOffset,M.byteOffset+M.byteLength)}async function k(M){let I=await crypto.subtle.digest("SHA-256",J(M));return G(new Uint8Array(I)).replace(/\r\n/g,"")}async function L(M,I,x){let K=J(I);if(x==="ed25519-sha256"){let Y=await crypto.subtle.sign("Ed25519",M,K);return new Uint8Array(Y)}let S=await crypto.subtle.sign({name:"RSASSA-PKCS1-v1_5"},M,K);return new Uint8Array(S)}export{N as signDKIM,v as importPrivateKey,_ as canonicalizeHeadersRelaxed,$ as canonicalizeBodyRelaxed};
|
|
8
8
|
|
|
9
|
-
//# debugId=
|
|
9
|
+
//# debugId=9D9099D2DDB76FD564756E2164756E21
|
package/dist/dkim.js.map
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * DKIM signing implementation (RFC 6376). Public entry: `sently/dkim`.\n */\nimport { encodeBase64, encodeUtf8 } from \"./base64.js\";\nimport type { DKIMConfig } from \"./types.js\";\n\nconst CRLF = \"\\r\\n\";\nconst DEFAULT_HEADER_FIELDS = \"from:to:subject:date:message-id:mime-version:content-type\";\n\n/** Result of DKIM signing — the header line to prepend. */\nexport interface DKIMSignResult {\n /** Complete DKIM-Signature header line (without trailing CRLF). */\n header: string;\n}\n\n/**\n * Canonicalize headers using the relaxed algorithm (RFC 6376 §3.4.2).\n */\nexport function canonicalizeHeadersRelaxed(headers: string, fieldNames: string[]): string {\n const parsed = parseHeaders(headers);\n const lines: string[] = [];\n\n for (const name of fieldNames) {\n const key = name.toLowerCase().trim();\n const values = parsed.get(key);\n if (!values) {\n continue;\n }\n for (const value of values) {\n const unfolded = value.replace(/\\r?\\n/g, \"\").replace(/\\s+/g, \" \").trim();\n lines.push(`${key}:${unfolded}`);\n }\n }\n\n return lines.length > 0 ? `${lines.join(CRLF)}${CRLF}` : \"\";\n}\n\n/**\n * Canonicalize body using the relaxed algorithm (RFC 6376 §3.4.4).\n */\nexport function canonicalizeBodyRelaxed(body: string): string {\n const normalized = body.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const lines = normalized\n .split(\"\\n\")\n .map((line) => line.replace(/[ \\t]+$/g, \"\").replace(/[ \\t]+/g, \" \"))\n .filter((line) => line.length > 0);\n if (lines.length === 0) {\n return CRLF;\n }\n return `${lines.join(CRLF)}${CRLF}`;\n}\n\n/**\n * Import a PEM-encoded private key into a Web Crypto CryptoKey.\n */\nexport async function importPrivateKey(\n pem: string,\n algorithm: \"rsa-sha256\" | \"ed25519-sha256\",\n): Promise<CryptoKey> {\n if (algorithm === \"ed25519-sha256\" && /OPENSSH PRIVATE KEY/i.test(pem)) {\n throw new Error(\n \"Ed25519 keys must be in PKCS#8 PEM format (-----BEGIN PRIVATE KEY-----). Convert with: openssl pkcs8 -topk8 -nocrypt -in key.pem -out key_pkcs8.pem\",\n );\n }\n\n const der = pemToDer(pem);\n\n const derBuffer = toArrayBuffer(der);\n\n try {\n if (algorithm === \"ed25519-sha256\") {\n return await crypto.subtle.importKey(\"pkcs8\", derBuffer, { name: \"Ed25519\" }, false, [\n \"sign\",\n ]);\n }\n return await crypto.subtle.importKey(\n \"pkcs8\",\n derBuffer,\n { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n } catch (err) {\n if (algorithm === \"ed25519-sha256\" && err instanceof DOMException) {\n throw new Error(\"Ed25519 DKIM requires Node.js ≥ 18.4, Bun ≥ 1.0, or Cloudflare Workers\", {\n cause: err,\n });\n }\n throw err;\n }\n}\n\n/**\n * Sign a raw MIME message with DKIM.\n */\nexport async function signDKIM(\n rawMessage: Uint8Array,\n config: DKIMConfig,\n): Promise<DKIMSignResult> {\n const algorithm = config.algorithm ?? \"rsa-sha256\";\n const fieldList = (config.headerFieldNames ?? DEFAULT_HEADER_FIELDS)\n .split(\":\")\n .map((f) => f.trim().toLowerCase())\n .filter(Boolean);\n const skip = new Set(\n (config.skipFields ?? \"\")\n .split(\":\")\n .map((f) => f.trim().toLowerCase())\n .filter(Boolean),\n );\n const signFields = fieldList.filter((f) => !skip.has(f));\n\n const text = new TextDecoder().decode(rawMessage);\n const sep = text.indexOf(\"\\r\\n\\r\\n\");\n const headerPart = sep >= 0 ? text.slice(0, sep) : text;\n const bodyPart = sep >= 0 ? text.slice(sep + 4) : \"\";\n\n const bodyCanonical = canonicalizeBodyRelaxed(bodyPart);\n const bodyHash = await sha256Base64(encodeUtf8(bodyCanonical));\n\n const dkimAlgo = algorithm === \"ed25519-sha256\" ? \"ed25519-sha256\" : \"rsa-sha256\";\n const headerList = signFields.join(\":\");\n const timestamp = Math.floor(Date.now() / 1000);\n\n const dkimWithoutSig = [\n `v=1`,\n `a=${dkimAlgo}`,\n `c=relaxed/relaxed`,\n `d=${config.domainName}`,\n `s=${config.keySelector}`,\n `h=${headerList}`,\n `bh=${bodyHash}`,\n `b=`,\n `t=${timestamp}`,\n ].join(\"; \");\n\n const dkimHeaderName = \"dkim-signature\";\n const dkimHeaderValue = dkimWithoutSig;\n const headersWithDkim = `${headerPart}${CRLF}${dkimHeaderName}:${dkimHeaderValue}`;\n const canonical = canonicalizeHeadersRelaxed(headersWithDkim, [...signFields, dkimHeaderName]);\n\n const key = await importPrivateKey(config.privateKey, algorithm);\n const data = encodeUtf8(canonical);\n const signature = await signData(key, data, algorithm);\n const bValue = encodeBase64(signature).replace(/\\r\\n/g, \"\");\n\n const header = `DKIM-Signature: v=1; a=${dkimAlgo}; c=relaxed/relaxed; d=${config.domainName}; s=${config.keySelector}; h=${headerList}; bh=${bodyHash}; b=${bValue}; t=${timestamp}`;\n\n return { header };\n}\n\nfunction parseHeaders(headerBlock: string): Map<string, string[]> {\n const map = new Map<string, string[]>();\n const lines = headerBlock.split(/\\r?\\n/).filter((l) => l.length > 0);\n let currentName = \"\";\n let currentValue = \"\";\n\n const flush = (): void => {\n if (!currentName) {\n return;\n }\n const key = currentName.toLowerCase();\n const list = map.get(key) ?? [];\n list.push(currentValue);\n map.set(key, list);\n currentName = \"\";\n currentValue = \"\";\n };\n\n for (const line of lines) {\n if (/^[ \\t]/.test(line) && currentName) {\n currentValue += ` ${line.trim()}`;\n continue;\n }\n flush();\n const colon = line.indexOf(\":\");\n if (colon > 0) {\n currentName = line.slice(0, colon).trim();\n currentValue = line.slice(colon + 1).trim();\n }\n }\n flush();\n return map;\n}\n\nfunction pemToDer(pem: string): Uint8Array {\n const lines = pem\n .replace(/-----BEGIN[^-]+-----/g, \"\")\n .replace(/-----END[^-]+-----/g, \"\")\n .replace(/\\s/g, \"\");\n const binary = atob(lines);\n const der = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n der[i] = binary.charCodeAt(i);\n }\n return der;\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nasync function sha256Base64(data: Uint8Array): Promise<string> {\n const hash = await crypto.subtle.digest(\"SHA-256\", toArrayBuffer(data));\n return encodeBase64(new Uint8Array(hash)).replace(/\\r\\n/g, \"\");\n}\n\nasync function signData(\n key: CryptoKey,\n data: Uint8Array,\n algorithm: \"rsa-sha256\" | \"ed25519-sha256\",\n): Promise<Uint8Array> {\n const buf = toArrayBuffer(data);\n if (algorithm === \"ed25519-sha256\") {\n const sig = await crypto.subtle.sign(\"Ed25519\", key, buf);\n return new Uint8Array(sig);\n }\n const sig = await crypto.subtle.sign({ name: \"RSASSA-PKCS1-v1_5\" }, key, buf);\n return new Uint8Array(sig);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "9D9099D2DDB76FD564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/idempotency.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{J as F}from"./chunk-bhr7w3gg.js";import{S as C,U as B}from"./chunk-8kpgbrba.js";import"./chunk-hnzmn4s4.js";import"./chunk-th4cwrpb.js";class G{entries=new Map;async has(q){let w=this.entries.get(q);if(w===void 0)return!1;if(w<=Date.now())return this.entries.delete(q),!1;return!0}async set(q,w){let z=w!==void 0?Date.now()+w:Number.POSITIVE_INFINITY;this.entries.set(q,z)}}function H(q){let w=C(q.from)[0],z=[...B(q.to),...q.cc?B(q.cc):[],...q.bcc?B(q.bcc):[]];return{messageId:q.messageId??"",accepted:B(q.to),rejected:[],response:"Deduped — idempotency key already recorded",envelope:{from:w?.address??"",to:z},deduped:!0}}class J{inner;provider="idempotency";store;ttlMs;constructor(q,w){this.inner=q;this.store=w?.store??new G,this.ttlMs=w?.ttlMs??86400000}async send(q){let w=F(q);if(w===void 0)return this.inner.send(q);if(await this.store.has(w))return H(q);let z=await this.inner.send(q);return await this.store.set(w,this.ttlMs),z}async sendBatch(q){if(this.inner.sendBatch)return this.inner.sendBatch(q);return Promise.all(q.map((w)=>this.send(w)))}async verify(){if(this.inner.verify)return this.inner.verify();return{ok:!0,provider:"idempotency"}}async close(){await this.inner.close?.()}}export{G as MemoryIdempotencyStore,J as IdempotencyTransport};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=79816066E787AB7564756E2164756E21
|
package/dist/idempotency.js.map
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"/**\n * @module\n * IdempotencyTransport — decorator that deduplicates sends on retry or replay.\n *\n * Wrap **outside** RetryTransport so all retry attempts share one key:\n *\n * ```ts\n * new IdempotencyTransport(new RetryTransport(inner))\n * ```\n *\n * For production, supply a shared store (Redis, Dragonfly, etc.).\n *\n * @example\n * ```ts\n * import { IdempotencyTransport, MemoryIdempotencyStore } from \"sently/idempotency\";\n * import { RetryTransport } from \"sently/transports/retry\";\n * import { ResendTransport } from \"sently/transports/resend\";\n *\n * const transport = new IdempotencyTransport(\n * new RetryTransport(new ResendTransport({ apiKey })),\n * { store: new MemoryIdempotencyStore(), ttlMs: 86_400_000 },\n * );\n * ```\n */\nimport { extractEmails, parseAddresses } from \"./core/address.js\";\nimport { resolveIdempotencyKey } from \"./core/idempotency-key.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"./core/types.js\";\n\n/** Key-value store for idempotency deduplication. */\nexport interface IdempotencyStore {\n /** Returns true when the key was already recorded. */\n has(key: string): Promise<boolean>;\n /** Record a key, optionally expiring after `ttlMs` milliseconds. */\n set(key: string, ttlMs?: number): Promise<void>;\n}\n\n/** In-memory idempotency store with optional TTL expiry. Suitable for single-process use. */\nexport class MemoryIdempotencyStore implements IdempotencyStore {\n private readonly entries = new Map<string, number>();\n\n /** Returns true when the key exists and has not expired. */\n async has(key: string): Promise<boolean> {\n const expiresAt = this.entries.get(key);\n if (expiresAt === undefined) {\n return false;\n }\n if (expiresAt <= Date.now()) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n /** Record a key with optional TTL in milliseconds. */\n async set(key: string, ttlMs?: number): Promise<void> {\n const expiresAt = ttlMs !== undefined ? Date.now() + ttlMs : Number.POSITIVE_INFINITY;\n this.entries.set(key, expiresAt);\n }\n}\n\n/** Options for {@link IdempotencyTransport}. */\nexport interface IdempotencyTransportOptions {\n /** Store used to track sent keys. Defaults to {@link MemoryIdempotencyStore}. */\n store?: IdempotencyStore;\n /** TTL in milliseconds for recorded keys. Default: 24 hours. */\n ttlMs?: number;\n}\n\nfunction syntheticDedupedResult(options: MailOptions): SendResult {\n const from = parseAddresses(options.from)[0];\n const to = [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ];\n\n return {\n messageId: options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: \"Deduped — idempotency key already recorded\",\n envelope: {\n from: from?.address ?? \"\",\n to,\n },\n deduped: true,\n };\n}\n\n/**\n * Transport decorator that skips duplicate sends when an idempotency key\n * was already recorded. Checks the store once before delegating to the inner\n * transport, so retries inside RetryTransport reuse the same key automatically.\n */\nexport class IdempotencyTransport implements Transport {\n readonly provider = \"idempotency\";\n\n private readonly store: IdempotencyStore;\n private readonly ttlMs: number;\n\n /** Wraps an inner transport with idempotency deduplication. */\n constructor(\n /** Transport that performs the actual send when the key is new. */\n private readonly inner: Transport,\n options?: IdempotencyTransportOptions,\n ) {\n this.store = options?.store ?? new MemoryIdempotencyStore();\n this.ttlMs = options?.ttlMs ?? 86_400_000;\n }\n\n /** Sends once per idempotency key; returns a synthetic result on duplicate keys. */\n async send(options: MailOptions): Promise<SendResult> {\n const key = resolveIdempotencyKey(options);\n if (key === undefined) {\n return this.inner.send(options);\n }\n\n if (await this.store.has(key)) {\n return syntheticDedupedResult(options);\n }\n\n const result = await this.inner.send(options);\n await this.store.set(key, this.ttlMs);\n return result;\n }\n\n /** Delegates batch sends to the inner transport when available. */\n async sendBatch(messages: MailOptions[]): Promise<SendResult[]> {\n if (this.inner.sendBatch) {\n return this.inner.sendBatch(messages);\n }\n return Promise.all(messages.map((message) => this.send(message)));\n }\n\n /** Delegates to the inner transport verify or returns a default success result. */\n async verify(): Promise<VerifyResult> {\n if (this.inner.verify) {\n return this.inner.verify();\n }\n return { ok: true, provider: \"idempotency\" };\n }\n\n /** Delegates close to the inner transport if available. */\n async close(): Promise<void> {\n await this.inner.close?.();\n }\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "iKAqCO,AAAM,LAAmD,LAC7C,QAAU,IAAI,SAGzB,IAAG,CAAC,EAA+B,CACvC,IAAM,EAAY,KAAK,QAAQ,IAAI,CAAG,EACtC,GAAI,IAAc,OAChB,MAAO,GAET,GAAI,GAAa,KAAK,IAAI,EAExB,OADA,KAAK,QAAQ,OAAO,CAAG,EAChB,GAET,MAAO,QAIH,IAAG,CAAC,EAAa,EAA+B,CACpD,IAAM,EAAY,IAAU,OAAY,KAAK,IAAI,EAAI,EAAQ,OAAO,kBACpE,KAAK,QAAQ,IAAI,EAAK,CAAS,EAEnC,CAUA,SAAS,CAAsB,CAAC,EAAkC,CAChE,IAAM,EAAO,EAAe,EAAQ,IAAI,EAAE,GACpC,EAAK,CACT,GAAG,EAAc,EAAQ,EAAE,EAC3B,GAAI,EAAQ,GAAK,EAAc,EAAQ,EAAE,EAAI,CAAC,EAC9C,GAAI,EAAQ,IAAM,EAAc,EAAQ,GAAG,EAAI,CAAC,CAClD,EAEA,MAAO,CACL,UAAW,EAAQ,WAAa,GAChC,SAAU,EAAc,EAAQ,EAAE,EAClC,SAAU,CAAC,EACX,SAAU,6CACV,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,IACF,EACA,QAAS,EACX,EAQK,MAAM,CAA0C,CASlC,MARV,SAAW,cAEH,MACA,MAGjB,WAAW,CAEQ,EACjB,EACA,CAFiB,aAGjB,KAAK,MAAQ,GAAS,OAAS,IAAI,EACnC,KAAK,MAAQ,GAAS,OAAS,cAI3B,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAM,EAAsB,CAAO,EACzC,GAAI,IAAQ,OACV,OAAO,KAAK,MAAM,KAAK,CAAO,EAGhC,GAAI,MAAM,KAAK,MAAM,IAAI,CAAG,EAC1B,OAAO,EAAuB,CAAO,EAGvC,IAAM,EAAS,MAAM,KAAK,MAAM,KAAK,CAAO,EAE5C,OADA,MAAM,KAAK,MAAM,IAAI,EAAK,KAAK,KAAK,EAC7B,OAIH,UAAS,CAAC,EAAgD,CAC9D,GAAI,KAAK,MAAM,UACb,OAAO,KAAK,MAAM,UAAU,CAAQ,EAEtC,OAAO,QAAQ,IAAI,EAAS,IAAI,CAAC,IAAY,KAAK,KAAK,CAAO,CAAC,CAAC,OAI5D,OAAM,EAA0B,CACpC,GAAI,KAAK,MAAM,OACb,OAAO,KAAK,MAAM,OAAO,EAE3B,MAAO,CAAE,GAAI,GAAM,SAAU,aAAc,OAIvC,MAAK,EAAkB,CAC3B,MAAM,KAAK,MAAM,QAAQ,EAE7B",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "79816066E787AB7564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ GOOGLE_TOKEN_URL,
|
|
|
32
32
|
MICROSOFT_TOKEN_URL,
|
|
33
33
|
/** OAuth2 client with in-memory token cache and automatic refresh. */
|
|
34
34
|
OAuth2Client, } from "./auth/oauth2.js";
|
|
35
|
+
export type { AnySendResult, ChannelSendResult } from "./channel-result.js";
|
|
36
|
+
export {
|
|
37
|
+
/** Normalize any channel send result to `{ messageId, provider, accepted }`. */
|
|
38
|
+
toChannelSendResult, } from "./channel-result.js";
|
|
35
39
|
export type { SentlyErrorCode, SentlyErrorOptions } from "./core/errors.js";
|
|
36
40
|
export {
|
|
37
41
|
/** Map an HTTP status code to a stable sently error code. */
|
package/dist/index.js
CHANGED
package/dist/mailer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{N as a,O as b}from"./chunk-w9sabmb0.js";import"./chunk-cs1tvehn.js";import"./chunk-7wtfjx0d.js";import"./chunk-8kpgbrba.js";import"./chunk-htdg075t.js";import"./chunk-hnzmn4s4.js";import"./chunk-ttbwyxmh.js";import"./chunk-th4cwrpb.js";export{a as createMailer,b as MailerImpl};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=46F368F63655285E64756E2164756E21
|
package/dist/mailer.js.map
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"../chunk-
|
|
1
|
+
import"../chunk-th4cwrpb.js";function E(A="[sently]"){return{onSend:({subject:q})=>{console.log(`${A} sending: ${q}`)},onSuccess:({subject:q},C,z)=>{let B=z!==void 0?` (${z.toFixed(0)}ms)`:"";console.log(`${A} sent: ${q}${B}`)},onError:({subject:q},C,z)=>{let B=z!==void 0?` (${z.toFixed(0)}ms)`:"";console.error(`${A} failed: ${q}${B}`,C)}}}export{E as consoleObserver};
|
|
2
2
|
|
|
3
3
|
//# debugId=0D8FEC4487F45FD464756E2164756E21
|
package/dist/plugins/react.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{ma as B}from"../chunk-th4cwrpb.js";function F(){return async(j)=>{if(j.react===void 0||j.react===null)return j;let{render:k}=await import("@react-email/render"),q=j.html??await k(j.react,{pretty:!1}),z=j.text??await k(j.react,{plainText:!0}),{react:D,...A}=j;return{...A,html:q,text:z}}}export{F as reactPlugin};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=B4EBE2E4DC1CDD3664756E2164756E21
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"/**\n * @module\n * React Email plugin for sently.\n * Renders a React element to HTML and plain text via `@react-email/render`.\n *\n * @example\n * ```ts\n * import { reactPlugin } from \"sently/react\";\n * import { WelcomeEmail } from \"./emails/welcome\";\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey }),\n * plugins: [reactPlugin()],\n * });\n *\n * await mailer.send({\n * from: \"...\",\n * to: \"...\",\n * subject: \"Welcome!\",\n * react: WelcomeEmail({ name: \"Ali\" }),\n * });\n * ```\n */\nimport type { MailOptions, MailPlugin } from \"../core/types.js\";\n\n/** Mail options with a React element body (requires `react` peer). */\nexport interface ReactMailOptions extends Omit<MailOptions, \"react\"> {\n /** React element rendered to html + text by the react plugin. */\n react?: import(\"react\").ReactElement;\n}\n\n/**\n * Create a plugin that renders `options.react` to HTML and plain text.\n * Explicit `html` / `text` values always win. The `react` field is stripped\n * before options reach the transport.\n */\nexport function reactPlugin(): MailPlugin {\n return async (options: MailOptions): Promise<MailOptions> => {\n if (options.react === undefined || options.react === null) {\n return options;\n }\n\n const { render } = await import(\"@react-email/render\");\n\n const html =\n options.html ??\n (await render(options.react as Parameters<typeof render>[0], { pretty: false }));\n\n const text =\n options.text ??\n (await render(options.react as Parameters<typeof render>[0], { plainText: true }));\n\n const { react: _react, ...rest } = options;\n return { ...rest, html, text };\n };\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "0CAoCO,SAAS,CAAW,EAAe,CACxC,MAAO,OAAO,IAA+C,CAC3D,GAAI,EAAQ,QAAU,QAAa,EAAQ,QAAU,KACnD,OAAO,EAGT,IAAQ,UAAW,KAAa,+BAE1B,EACJ,EAAQ,MACP,MAAM,EAAO,EAAQ,MAAuC,CAAE,OAAQ,EAAM,CAAC,EAE1E,EACJ,EAAQ,MACP,MAAM,EAAO,EAAQ,MAAuC,CAAE,UAAW,EAAK,CAAC,GAE1E,MAAO,KAAW,GAAS,EACnC,MAAO,IAAK,EAAM,OAAM,MAAK",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "B4EBE2E4DC1CDD3664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|