@fabrika/email 0.8.1 → 0.9.0-cloudflare.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ import { z } from 'zod';
2
+ import { type EmailProvider } from './config.js';
3
+ import { type EmailDeliveryResult, type EmailMessage, type EmailSender, type EmailSenderCapabilities } from './email.js';
4
+ declare const capturedEmailSchema: z.ZodObject<{
5
+ messageId: z.ZodString;
6
+ idempotencyKey: z.ZodString;
7
+ to: z.ZodString;
8
+ subject: z.ZodString;
9
+ text: z.ZodString;
10
+ html: z.ZodOptional<z.ZodString>;
11
+ replyTo: z.ZodOptional<z.ZodString>;
12
+ fromName: z.ZodOptional<z.ZodString>;
13
+ unsubscribeUrl: z.ZodOptional<z.ZodString>;
14
+ broadcast: z.ZodOptional<z.ZodLiteral<true>>;
15
+ attachments: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodObject<{
16
+ filename: z.ZodString;
17
+ contentType: z.ZodString;
18
+ content: z.ZodString;
19
+ }, "strip", z.ZodTypeAny, {
20
+ filename: string;
21
+ contentType: string;
22
+ content: string;
23
+ }, {
24
+ filename: string;
25
+ contentType: string;
26
+ content: string;
27
+ }>, "many">>>;
28
+ capturedAt: z.ZodNumber;
29
+ }, "strip", z.ZodTypeAny, {
30
+ messageId: string;
31
+ idempotencyKey: string;
32
+ to: string;
33
+ subject: string;
34
+ text: string;
35
+ capturedAt: number;
36
+ html?: string | undefined;
37
+ replyTo?: string | undefined;
38
+ fromName?: string | undefined;
39
+ unsubscribeUrl?: string | undefined;
40
+ broadcast?: true | undefined;
41
+ attachments?: readonly {
42
+ filename: string;
43
+ contentType: string;
44
+ content: string;
45
+ }[] | undefined;
46
+ }, {
47
+ messageId: string;
48
+ idempotencyKey: string;
49
+ to: string;
50
+ subject: string;
51
+ text: string;
52
+ capturedAt: number;
53
+ html?: string | undefined;
54
+ replyTo?: string | undefined;
55
+ fromName?: string | undefined;
56
+ unsubscribeUrl?: string | undefined;
57
+ broadcast?: true | undefined;
58
+ attachments?: readonly {
59
+ filename: string;
60
+ contentType: string;
61
+ content: string;
62
+ }[] | undefined;
63
+ }>;
64
+ export type CapturedEmail = z.infer<typeof capturedEmailSchema>;
65
+ export interface DirectoryEmailSenderOptions {
66
+ /** An existing directory, as an absolute path. */
67
+ readonly directory: string;
68
+ readonly now?: () => number;
69
+ }
70
+ export declare class DirectoryEmailSender implements EmailSender {
71
+ #private;
72
+ readonly capabilities: EmailSenderCapabilities;
73
+ constructor(options: DirectoryEmailSenderOptions);
74
+ /** One file per idempotency key: a repeated send of the same message answers the first id, as a provider does. */
75
+ send(message: EmailMessage, signal: AbortSignal): Promise<EmailDeliveryResult>;
76
+ }
77
+ /**
78
+ * `FABRIKA_EMAIL_PROVIDER=directory`: a local composition's capture. Never in `builtinEmailProviders`; a composition
79
+ * registers it. The capture records content only, so `FABRIKA_EMAIL_FROM` is not used.
80
+ */
81
+ export declare const directoryEmailProvider: EmailProvider;
82
+ /** Every message a `DirectoryEmailSender` captured in `directory`, oldest first. */
83
+ export declare function readCapturedEmails(directory: string): Promise<CapturedEmail[]>;
84
+ export {};
@@ -0,0 +1,142 @@
1
+ export type EmailFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
2
+ /** One file a message carries: the name the recipient sees, its media type and its bytes. */
3
+ export interface EmailAttachment {
4
+ readonly filename: string;
5
+ readonly contentType: string;
6
+ readonly content: Uint8Array;
7
+ }
8
+ export interface EmailMessage {
9
+ readonly to: string;
10
+ readonly subject: string;
11
+ readonly text: string;
12
+ readonly html?: string;
13
+ /** 1..256 visible ASCII characters (Resend's limit). A prefix the caller adds must fit inside it. */
14
+ readonly idempotencyKey: string;
15
+ /** One mailbox address. */
16
+ readonly replyTo?: string;
17
+ /** Replaces the transport's display name for this message. */
18
+ readonly fromName?: string;
19
+ /**
20
+ * An `https:` URL that unsubscribes the recipient in one click (RFC 8058): the transport writes `List-Unsubscribe:
21
+ * <url>` and `List-Unsubscribe-Post: List-Unsubscribe=One-Click`. A transport without `listUnsubscribe` refuses it.
22
+ */
23
+ readonly unsubscribeUrl?: string;
24
+ /**
25
+ * One message of a send to many recipients (a newsletter, an announcement), not a reply to what the recipient did.
26
+ * A provider keeps such mail apart from transactional mail, and the law and the mailbox providers ask it to carry
27
+ * an unsubscribe, so it needs `unsubscribeUrl`. A transport without `broadcast` refuses it.
28
+ */
29
+ readonly broadcast?: boolean;
30
+ /**
31
+ * Files the message carries, at most `MAX_EMAIL_ATTACHMENTS`, together at most the transport's `maxAttachmentBytes`.
32
+ * A transport without `maxAttachmentBytes` refuses a message that has any; an empty list is none.
33
+ */
34
+ readonly attachments?: readonly EmailAttachment[];
35
+ }
36
+ export interface EmailDeliveryResult {
37
+ readonly status: 'accepted';
38
+ readonly provider: string;
39
+ readonly messageId: string;
40
+ }
41
+ export interface EmailSenderCapabilities {
42
+ /** A repeated send under one key with the same message is not delivered again. */
43
+ readonly idempotent: boolean;
44
+ /** 'sync': `accepted` means the provider took the message. 'queued': it queued the request and may still drop it. */
45
+ readonly confirmation: 'sync' | 'queued';
46
+ /** The most messages one call carries. `send` carries one, so every built-in adapter reports 1. */
47
+ readonly maxBatch: number;
48
+ /** Whether it writes a message's one-click unsubscribe headers (`unsubscribeUrl`); absent is no. */
49
+ readonly listUnsubscribe?: boolean;
50
+ /** The most attachment bytes one message carries, every file together; absent: it sends no attachments. */
51
+ readonly maxAttachmentBytes?: number;
52
+ /** File extensions it refuses an attachment for, lowercase and without the dot; absent: none. */
53
+ readonly refusedAttachmentExtensions?: readonly string[];
54
+ /** The fewest characters a text or HTML body has once trimmed; absent: any body that is not blank. */
55
+ readonly minBodyLength?: number;
56
+ /** Whether it sends a `broadcast` message, on its provider's channel for them where it has one; absent is no. */
57
+ readonly broadcast?: boolean;
58
+ }
59
+ export interface EmailSender {
60
+ readonly capabilities: EmailSenderCapabilities;
61
+ send(message: EmailMessage, signal: AbortSignal): Promise<EmailDeliveryResult>;
62
+ }
63
+ /** 'not_sent': the provider did not take the message. 'unknown': it may have taken it. */
64
+ export type EmailDeliveryOutcome = 'not_sent' | 'unknown';
65
+ /** The one classification of every failure: an adapter names the code, and the code decides the rest. */
66
+ export declare const EMAIL_DELIVERY_ERRORS: {
67
+ readonly invalid_configuration: {
68
+ readonly retryable: false;
69
+ readonly outcome: "not_sent";
70
+ };
71
+ readonly invalid_message: {
72
+ readonly retryable: false;
73
+ readonly outcome: "not_sent";
74
+ };
75
+ readonly mail_not_configured: {
76
+ readonly retryable: false;
77
+ readonly outcome: "not_sent";
78
+ };
79
+ readonly aborted: {
80
+ readonly retryable: true;
81
+ readonly outcome: "unknown";
82
+ };
83
+ readonly network_error: {
84
+ readonly retryable: true;
85
+ readonly outcome: "unknown";
86
+ };
87
+ readonly request_timeout: {
88
+ readonly retryable: true;
89
+ readonly outcome: "unknown";
90
+ };
91
+ readonly request_in_progress: {
92
+ readonly retryable: true;
93
+ readonly outcome: "unknown";
94
+ };
95
+ readonly idempotency_conflict: {
96
+ readonly retryable: false;
97
+ readonly outcome: "unknown";
98
+ };
99
+ readonly provider_unauthorized: {
100
+ readonly retryable: false;
101
+ readonly outcome: "not_sent";
102
+ };
103
+ readonly rate_limited: {
104
+ readonly retryable: true;
105
+ readonly outcome: "not_sent";
106
+ };
107
+ readonly quota_exceeded: {
108
+ readonly retryable: true;
109
+ readonly outcome: "not_sent";
110
+ };
111
+ readonly provider_rejected: {
112
+ readonly retryable: false;
113
+ readonly outcome: "not_sent";
114
+ };
115
+ readonly provider_unavailable: {
116
+ readonly retryable: true;
117
+ readonly outcome: "unknown";
118
+ };
119
+ readonly malformed_response: {
120
+ readonly retryable: true;
121
+ readonly outcome: "unknown";
122
+ };
123
+ };
124
+ export type EmailDeliveryErrorCode = keyof typeof EMAIL_DELIVERY_ERRORS;
125
+ export interface EmailDeliveryErrorOptions {
126
+ readonly code: EmailDeliveryErrorCode;
127
+ readonly httpStatus?: number;
128
+ }
129
+ /** Its message is a fixed string plus the HTTP status: never a credential, a response body, a subject or a body. */
130
+ export declare class EmailDeliveryError extends Error {
131
+ readonly code: EmailDeliveryErrorCode;
132
+ readonly retryable: boolean;
133
+ readonly outcome: EmailDeliveryOutcome;
134
+ readonly httpStatus: number | undefined;
135
+ constructor(message: string, options: EmailDeliveryErrorOptions);
136
+ }
137
+ /** The two headers of a one-click unsubscribe (RFC 8058), as a transport writes them for `unsubscribeUrl`. */
138
+ export declare function oneClickUnsubscribeHeaders(url: string): Readonly<Record<string, string>>;
139
+ /** The one retry rule: an unknown outcome is retried only over an idempotent transport. */
140
+ export declare function mayRetryDelivery(error: EmailDeliveryError, capabilities: EmailSenderCapabilities): boolean;
141
+ /** The bytes of every attachment of `message` together. */
142
+ export declare function attachmentBytes(message: EmailMessage): number;
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ import type { EmailSenderIdentity } from './config.js';
3
+ import { EmailDeliveryError, type EmailDeliveryResult, type EmailFetch, type EmailMessage, type EmailSender, type EmailSenderCapabilities } from './email.js';
4
+ import { type ProviderResponse } from './http.js';
5
+ /** The options every HTTP adapter takes beside its credentials. */
6
+ export interface HttpEmailSenderOptions {
7
+ readonly from: EmailSenderIdentity;
8
+ readonly fetch?: EmailFetch;
9
+ readonly timeoutMs?: number;
10
+ }
11
+ export interface HttpEmailRequest {
12
+ readonly url: string;
13
+ readonly headers: Readonly<Record<string, string>>;
14
+ readonly body: unknown;
15
+ }
16
+ export declare abstract class HttpEmailSender implements EmailSender {
17
+ #private;
18
+ abstract readonly capabilities: EmailSenderCapabilities;
19
+ protected readonly from: EmailSenderIdentity;
20
+ /** `label` names the provider in every message, `provider` in every result. An adapter checks its credentials first. */
21
+ protected constructor(names: {
22
+ readonly label: string;
23
+ readonly provider: string;
24
+ }, options: HttpEmailSenderOptions);
25
+ /** The request for a message that passed validation. */
26
+ protected abstract request(message: EmailMessage): HttpEmailRequest | Promise<HttpEmailRequest>;
27
+ /** The message id of a 2xx answer to `message`; undefined when the answer is not a valid success. May throw a refusal. */
28
+ protected abstract messageId(response: ProviderResponse, message: EmailMessage): string | undefined;
29
+ /** A failed answer to the message the provider tells apart beyond its status; undefined leaves it to `refusalByStatus`. */
30
+ protected refusal(_response: ProviderResponse, _message: EmailMessage): EmailDeliveryError | undefined;
31
+ send(message: EmailMessage, signal: AbortSignal): Promise<EmailDeliveryResult>;
32
+ }
33
+ /** The classification of a failed answer by its status alone, for every answer an adapter does not tell apart. */
34
+ export declare function refusalByStatus(label: string, status: number): EmailDeliveryError;
35
+ /** `Name <address>` for an API that takes the sender as one header value; validation keeps the name quotable. */
36
+ export declare function formatMailbox(address: string, name: string | undefined): string;
37
+ /** An API key or token: non-empty visible ASCII, so it fits a header unchanged. */
38
+ export declare function isCredential(value: string): boolean;
39
+ /** The name of a provider's stream or route: 1..64 letters, digits, `_` and `-`. */
40
+ export declare function isChannelName(value: string): boolean;
41
+ /** A provider variable that holds a credential. */
42
+ export declare const credentialVariable: z.ZodEffects<z.ZodString, string, string>;
43
+ /** An optional provider variable: empty is absent, and a present value must pass `valid`. */
44
+ export declare function optionalVariable(valid: (value: string) => boolean): z.ZodEffects<z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>, string | undefined, string | undefined>;
45
+ /**
46
+ * The message's key as a provider that takes at most `maxLength` characters sends it. A longer key is sent as its
47
+ * SHA-256, which keeps one key one key: a retry sends the same digest, another message another.
48
+ */
49
+ export declare function providerIdempotencyKey(key: string, maxLength: number): Promise<string>;
@@ -0,0 +1,20 @@
1
+ import { type EmailFetch } from './email.js';
2
+ export interface ProviderRequest {
3
+ /** The provider's name as it appears in error messages. */
4
+ readonly label: string;
5
+ readonly fetch: EmailFetch;
6
+ readonly url: string;
7
+ readonly headers: Readonly<Record<string, string>>;
8
+ readonly body: unknown;
9
+ readonly signal: AbortSignal;
10
+ readonly timeoutMs: number;
11
+ }
12
+ export interface ProviderResponse {
13
+ readonly ok: boolean;
14
+ readonly status: number;
15
+ /** The parsed JSON body, or undefined when it is not JSON. Never put into an error. */
16
+ readonly body: unknown;
17
+ }
18
+ /** The answer with its body already read, so an abort or a timeout while the body arrives is classified as one. */
19
+ export declare function postToProvider(request: ProviderRequest): Promise<ProviderResponse>;
20
+ export declare function parseTimeout(label: string, value: number | undefined): number;
@@ -0,0 +1,5 @@
1
+ export { EMAIL_PROVIDER_VARIABLE, emailSenderFromEnv, NO_EMAIL_PROVIDER, parseProviderVariables, pickEmailEnvironment, readEmailTransportConfig, } from './config.js';
2
+ export type { EmailEnvironment, EmailProvider, EmailProviderRegistry, EmailProviderVariable, EmailSenderIdentity, EmailTransportConfig, } from './config.js';
3
+ export { attachmentBytes, EMAIL_DELIVERY_ERRORS, EmailDeliveryError, mayRetryDelivery, oneClickUnsubscribeHeaders } from './email.js';
4
+ export type { EmailAttachment, EmailDeliveryErrorCode, EmailDeliveryErrorOptions, EmailDeliveryOutcome, EmailDeliveryResult, EmailFetch, EmailMessage, EmailSender, EmailSenderCapabilities, } from './email.js';
5
+ export { isUnsubscribeUrl, isValidAttachmentFilename, isValidEmailAddress, isValidEmailDisplayName, isValidEmailFrom, MAX_EMAIL_ATTACHMENTS, MAX_EMAIL_SUBJECT_LENGTH, MAX_UNSUBSCRIBE_URL_LENGTH, validateEmailAttachments, validateEmailBodies, validateEmailBroadcast, validateEmailMessage, } from './validation.js';
@@ -0,0 +1,19 @@
1
+ import { type EmailProvider } from './config.js';
2
+ import { EmailDeliveryError, type EmailMessage, type EmailSenderCapabilities } from './email.js';
3
+ import { type HttpEmailRequest, HttpEmailSender, type HttpEmailSenderOptions } from './http-sender.js';
4
+ import type { ProviderResponse } from './http.js';
5
+ export interface LettermintEmailSenderOptions extends HttpEmailSenderOptions {
6
+ /** A project API token: it sends through that project's default route. */
7
+ readonly projectToken: string;
8
+ /** The slug of the project's broadcast route, which a `broadcast` message goes through; `broadcast` by default. */
9
+ readonly broadcastRoute?: string;
10
+ }
11
+ export declare class LettermintEmailSender extends HttpEmailSender {
12
+ #private;
13
+ readonly capabilities: EmailSenderCapabilities;
14
+ constructor(options: LettermintEmailSenderOptions);
15
+ protected request(message: EmailMessage): Promise<HttpEmailRequest>;
16
+ protected messageId(response: ProviderResponse): string | undefined;
17
+ protected refusal(response: ProviderResponse, message: EmailMessage): EmailDeliveryError | undefined;
18
+ }
19
+ export declare const lettermintEmailProvider: EmailProvider;
@@ -0,0 +1,42 @@
1
+ import { type EmailDeliveryErrorCode, type EmailDeliveryResult, type EmailMessage, type EmailSender, type EmailSenderCapabilities } from './email.js';
2
+ export interface MemoryEmail {
3
+ readonly message: EmailMessage;
4
+ readonly messageId: string;
5
+ readonly acceptedAt: number;
6
+ }
7
+ export interface MemoryHandoff {
8
+ readonly message: EmailMessage;
9
+ readonly at: number;
10
+ /** Whether this call added the message to `accepted`. */
11
+ readonly taken: boolean;
12
+ readonly answer: 'accepted' | EmailDeliveryErrorCode;
13
+ }
14
+ export type MemoryRefusal = 'provider_rejected' | 'provider_unauthorized' | 'rate_limited' | 'quota_exceeded';
15
+ export interface MemoryEmailSenderOptions {
16
+ readonly capabilities?: Partial<EmailSenderCapabilities>;
17
+ readonly provider?: string;
18
+ readonly now?: () => number;
19
+ }
20
+ type MemoryHook = (message: EmailMessage, signal: AbortSignal) => Promise<void>;
21
+ export declare class MemoryEmailSender implements EmailSender {
22
+ #private;
23
+ readonly capabilities: EmailSenderCapabilities;
24
+ constructor(options?: MemoryEmailSenderOptions);
25
+ /** What reached the inbox, oldest first. */
26
+ get accepted(): readonly MemoryEmail[];
27
+ /** Every call that passed validation and the abort check. */
28
+ get handoffs(): readonly MemoryHandoff[];
29
+ /** Every following call is refused with `code` and takes nothing. */
30
+ refuse(code?: MemoryRefusal): void;
31
+ /** Every following call answers `provider_unavailable` and takes nothing. */
32
+ unavailable(): void;
33
+ /** Every following call answers `network_error` and takes nothing. */
34
+ unreachable(): void;
35
+ /** The next `count` calls take the message, then answer `network_error`. */
36
+ loseAnswers(count: number): void;
37
+ /** Runs after the handoff is recorded and before the answer; a rejection becomes `aborted` once the signal aborted, else `network_error`. */
38
+ during(hook: MemoryHook | null): void;
39
+ heal(): void;
40
+ send(message: EmailMessage, signal: AbortSignal): Promise<EmailDeliveryResult>;
41
+ }
42
+ export {};
@@ -0,0 +1,63 @@
1
+ import { type EmailDeliveryErrorCode, type EmailMessage, type EmailSenderCapabilities } from './email.js';
2
+ /** The relay's path under the receiver's URL (`FABRIKA_PLATFORM_MAIL_URL`). */
3
+ export declare const PLATFORM_MAIL_SEND_PATH = "/platform/mail/send";
4
+ /** What the relay promises: it records the message under its key and dispatches it later, perhaps after a person releases it. */
5
+ export declare const PLATFORM_MAIL_CAPABILITIES: EmailSenderCapabilities;
6
+ /**
7
+ * The request body as JSON text. It is canonical: two messages with the same content encode to the same text, so the
8
+ * relay fingerprints and stores this form.
9
+ */
10
+ export declare function encodePlatformMailRequest(message: EmailMessage): string;
11
+ /** The SHA-256 (hex) of the canonical request body: what tells a repeated send from a different one under the same key. */
12
+ export declare function platformMailFingerprint(message: EmailMessage): Promise<string>;
13
+ /** The SHA-256 (hex) of a transport key: the only form the receiver stores. */
14
+ export declare function hashPlatformKey(key: string): Promise<string>;
15
+ /**
16
+ * The key an installation service (IAM) sends to the relay with: derived from the installation secret both it and
17
+ * Control already hold, so an existing installation needs no new secret.
18
+ */
19
+ export declare function installationMailKey(installationSecret: string): Promise<string>;
20
+ export type PlatformMailRequestResult = {
21
+ readonly ok: true;
22
+ readonly message: EmailMessage;
23
+ }
24
+ /** `reason` is a fixed text that names no recipient, subject or body. */
25
+ | {
26
+ readonly ok: false;
27
+ readonly code: 'invalid_message';
28
+ readonly reason: string;
29
+ };
30
+ /** The relay's one parser of a request body (parsed JSON): a message the package's own validation accepts, or a refusal. */
31
+ export declare function parsePlatformMailRequest(body: unknown): PlatformMailRequestResult;
32
+ /** The HTTP status of each error the relay answers. */
33
+ export declare const PLATFORM_MAIL_ERROR_STATUS: {
34
+ readonly invalid_message: 400;
35
+ readonly provider_unauthorized: 401;
36
+ readonly idempotency_conflict: 409;
37
+ readonly mail_not_configured: 503;
38
+ readonly provider_unavailable: 503;
39
+ };
40
+ export type PlatformMailErrorCode = keyof typeof PLATFORM_MAIL_ERROR_STATUS;
41
+ export interface PlatformMailAccepted {
42
+ readonly status: 'accepted';
43
+ readonly messageId: string;
44
+ }
45
+ /** The relay's answer as a sender reads it: any `EmailDeliveryErrorCode`, so its `retryable` and `outcome` carry over. */
46
+ export type PlatformMailAnswer = PlatformMailAccepted | {
47
+ readonly error: {
48
+ readonly code: EmailDeliveryErrorCode;
49
+ };
50
+ };
51
+ /** The relay's answer as it writes one: an acceptance, or an error that has a status. */
52
+ export type PlatformMailReply = PlatformMailAccepted | {
53
+ readonly error: {
54
+ readonly code: PlatformMailErrorCode;
55
+ };
56
+ };
57
+ /** `200 { status: 'accepted', messageId }`, or the error's status with `{ error: { code } }`. */
58
+ export declare function platformMailAnswerResponse(reply: PlatformMailReply): Response;
59
+ /**
60
+ * The relay's answer from its status and parsed JSON body, or `null` when it is no answer the relay writes: an
61
+ * acceptance under any status but 200, an error code under a success status, or anything else.
62
+ */
63
+ export declare function parsePlatformMailAnswer(status: number, body: unknown): PlatformMailAnswer | null;
@@ -0,0 +1,19 @@
1
+ import { type EmailDeliveryResult, type EmailFetch, type EmailMessage, type EmailSender, type EmailSenderCapabilities } from './email.js';
2
+ export { encodePlatformMailRequest, hashPlatformKey, installationMailKey, parsePlatformMailAnswer, parsePlatformMailRequest, PLATFORM_MAIL_CAPABILITIES, PLATFORM_MAIL_ERROR_STATUS, PLATFORM_MAIL_SEND_PATH, platformMailAnswerResponse, platformMailFingerprint, } from './platform-wire.js';
3
+ export type { PlatformMailAccepted, PlatformMailAnswer, PlatformMailErrorCode, PlatformMailReply, PlatformMailRequestResult, } from './platform-wire.js';
4
+ /** `FABRIKA_EMAIL_PROVIDER=platform`: the application sends through the platform's relay, not a provider of its own. */
5
+ export declare const PLATFORM_EMAIL_PROVIDER = "platform";
6
+ export interface PlatformEmailSenderOptions {
7
+ /** The receiver's http(s) origin (`FABRIKA_PLATFORM_MAIL_URL`); the send path is appended to it. */
8
+ readonly url: string;
9
+ readonly key: string;
10
+ readonly fetch?: EmailFetch;
11
+ readonly timeoutMs?: number;
12
+ }
13
+ /** Hands each message to the platform's relay, which records it under its key and dispatches it later. */
14
+ export declare class PlatformEmailSender implements EmailSender {
15
+ #private;
16
+ readonly capabilities: EmailSenderCapabilities;
17
+ constructor(options: PlatformEmailSenderOptions);
18
+ send(message: EmailMessage, signal: AbortSignal): Promise<EmailDeliveryResult>;
19
+ }
@@ -0,0 +1,19 @@
1
+ import { type EmailProvider } from './config.js';
2
+ import { EmailDeliveryError, type EmailMessage, type EmailSenderCapabilities } from './email.js';
3
+ import { type HttpEmailRequest, HttpEmailSender, type HttpEmailSenderOptions } from './http-sender.js';
4
+ import type { ProviderResponse } from './http.js';
5
+ export interface PostmarkEmailSenderOptions extends HttpEmailSenderOptions {
6
+ /** A server token: it sends through that server's default transactional stream. */
7
+ readonly serverToken: string;
8
+ /** The ID of the server's broadcast stream, which a `broadcast` message goes through; `broadcast` by default. */
9
+ readonly broadcastStream?: string;
10
+ }
11
+ export declare class PostmarkEmailSender extends HttpEmailSender {
12
+ #private;
13
+ readonly capabilities: EmailSenderCapabilities;
14
+ constructor(options: PostmarkEmailSenderOptions);
15
+ protected request(message: EmailMessage): HttpEmailRequest;
16
+ protected messageId(response: ProviderResponse): string | undefined;
17
+ protected refusal(response: ProviderResponse): EmailDeliveryError | undefined;
18
+ }
19
+ export declare const postmarkEmailProvider: EmailProvider;
@@ -0,0 +1,29 @@
1
+ import type { EmailProviderVariable } from './config.js';
2
+ /**
3
+ * The providers `FABRIKA_EMAIL_PROVIDER` can name in a stock installation, sorted by name: the order every help text
4
+ * and refusal lists them in. Memory and Directory are composition inputs.
5
+ */
6
+ export declare const builtinEmailProviders: {
7
+ ahasend: import("./config.js").EmailProvider;
8
+ cloudflare: import("./config.js").EmailProvider;
9
+ lettermint: import("./config.js").EmailProvider;
10
+ postmark: import("./config.js").EmailProvider;
11
+ resend: import("./config.js").EmailProvider;
12
+ smartemailing: import("./config.js").EmailProvider;
13
+ };
14
+ /** Every built-in provider's own variables, in registry order: what the installers write and the workflow passes on. */
15
+ export declare const builtinEmailProviderVariables: readonly EmailProviderVariable[];
16
+ /**
17
+ * The first framework release whose registry holds each built-in provider. An application on an older release builds
18
+ * its sender from its own registry, so it cannot be handed one it lacks; Control refuses that deploy. `null`: every
19
+ * release that sends through the installation (`mailer: 'installation'`, from 0.5.0) holds it. A new provider names
20
+ * the release after the newest one published without it.
21
+ */
22
+ export declare const builtinEmailProviderSince: {
23
+ readonly ahasend: "0.8.2";
24
+ readonly cloudflare: "0.9.0";
25
+ readonly lettermint: "0.8.2";
26
+ readonly postmark: "0.8.2";
27
+ readonly resend: null;
28
+ readonly smartemailing: null;
29
+ };
@@ -0,0 +1,16 @@
1
+ import { type EmailProvider } from './config.js';
2
+ import { EmailDeliveryError, type EmailMessage, type EmailSenderCapabilities } from './email.js';
3
+ import { type HttpEmailRequest, HttpEmailSender, type HttpEmailSenderOptions } from './http-sender.js';
4
+ import type { ProviderResponse } from './http.js';
5
+ export interface ResendEmailSenderOptions extends HttpEmailSenderOptions {
6
+ readonly apiKey: string;
7
+ }
8
+ export declare class ResendEmailSender extends HttpEmailSender {
9
+ #private;
10
+ readonly capabilities: EmailSenderCapabilities;
11
+ constructor(options: ResendEmailSenderOptions);
12
+ protected request(message: EmailMessage): HttpEmailRequest;
13
+ protected messageId(response: ProviderResponse): string | undefined;
14
+ protected refusal(response: ProviderResponse): EmailDeliveryError | undefined;
15
+ }
16
+ export declare const resendEmailProvider: EmailProvider;
@@ -0,0 +1,24 @@
1
+ import { type EmailProvider } from './config.js';
2
+ import { type EmailMessage, type EmailSenderCapabilities } from './email.js';
3
+ import { type HttpEmailRequest, HttpEmailSender, type HttpEmailSenderOptions } from './http-sender.js';
4
+ import type { ProviderResponse } from './http.js';
5
+ export interface SmartEmailingEmailSenderOptions extends HttpEmailSenderOptions {
6
+ /** The account's user name or e-mail: the first half of the Basic credential. */
7
+ readonly username: string;
8
+ readonly apiKey: string;
9
+ /** Groups the messages in SmartEmailing's statistics. */
10
+ readonly tag?: string;
11
+ }
12
+ /** Its error body's `message` is free text that may quote the request, so a refusal is classified by status alone. */
13
+ export declare class SmartEmailingEmailSender extends HttpEmailSender {
14
+ #private;
15
+ /**
16
+ * The API takes up to five tasks a request, but `send` carries one. Its API documents no message headers, so a
17
+ * one-click unsubscribe is refused rather than sent without one.
18
+ */
19
+ readonly capabilities: EmailSenderCapabilities;
20
+ constructor(options: SmartEmailingEmailSenderOptions);
21
+ protected request(message: EmailMessage): HttpEmailRequest;
22
+ protected messageId(response: ProviderResponse): string | undefined;
23
+ }
24
+ export declare const smartEmailingEmailProvider: EmailProvider;
@@ -0,0 +1,35 @@
1
+ import type { EmailSenderIdentity } from './config.js';
2
+ import { type EmailMessage, type EmailSenderCapabilities } from './email.js';
3
+ /** The longest subject a message may have, in UTF-16 code units: RFC 5322's line limit. */
4
+ export declare const MAX_EMAIL_SUBJECT_LENGTH = 998;
5
+ export declare function isValidEmailAddress(value: string): boolean;
6
+ /** A mailbox address, or the legacy `Name <address>` form. */
7
+ export declare function isValidEmailFrom(value: string): boolean;
8
+ /**
9
+ * Splits a bare address or the legacy `Name <address>` form. A name in one pair of double quotes loses them, since a
10
+ * transport quotes the name itself when it has to.
11
+ */
12
+ export declare function parseEmailFrom(value: string): EmailSenderIdentity | null;
13
+ /** 1..128 characters, no control characters and none of `<>"\`, so a transport can always quote it. */
14
+ export declare function isValidEmailDisplayName(value: string): boolean;
15
+ export declare function isValidEmailSenderIdentity(identity: EmailSenderIdentity): boolean;
16
+ export declare function validateEmailMessage(message: EmailMessage): void;
17
+ /**
18
+ * Refuses a message whose attachments `capabilities` does not carry: any attachment where the transport sends none,
19
+ * more bytes together than its `maxAttachmentBytes`, or a file whose extension it refuses. `label` names the transport
20
+ * in the message.
21
+ */
22
+ export declare function validateEmailAttachments(message: EmailMessage, capabilities: EmailSenderCapabilities, label: string): void;
23
+ /** Refuses a `broadcast` message where the transport sends none. */
24
+ export declare function validateEmailBroadcast(message: EmailMessage, capabilities: EmailSenderCapabilities, label: string): void;
25
+ /** Refuses a text or HTML body shorter than the transport's `minBodyLength`, counted in characters once trimmed. */
26
+ export declare function validateEmailBodies(message: EmailMessage, capabilities: EmailSenderCapabilities, label: string): void;
27
+ /** The most files one message carries. */
28
+ export declare const MAX_EMAIL_ATTACHMENTS = 20;
29
+ /** 1..255 characters, no control characters, no path separator and no `"`, so every transport can quote it. */
30
+ export declare function isValidAttachmentFilename(value: string): boolean;
31
+ /** The longest unsubscribe URL a header carries: one header line, with room for its name. */
32
+ export declare const MAX_UNSUBSCRIBE_URL_LENGTH = 2048;
33
+ /** An absolute `https:` URL as it is written, which a `<…>` header value carries unchanged. */
34
+ export declare function isUnsubscribeUrl(value: string): boolean;
35
+ export declare function isVisibleAscii(value: string): boolean;