/** Bounce / complaint / unsubscribe webhook payload shape (ForumZone donor). */
type SuppressionEvent = {
    type: 'bounce' | 'complaint' | 'unsubscribe';
    email: string;
    provider?: string;
    raw?: unknown;
};
type MailMessage = {
    from: string;
    to: string | string[];
    replyTo?: string;
    cc?: string | string[];
    bcc?: string | string[];
    subject: string;
    html?: string;
    text?: string;
    headers?: Record<string, string>;
    tags?: Record<string, string>;
    /**
     * Provider-side dedup key — forwarded to the provider's native idempotency
     * mechanism where one exists (resend → Idempotency-Key header; brevo →
     * Idempotency-Key header). Postmark and SES have NO API-level idempotency, so
     * this field is dropped for those adapters (documented in each).
     *
     * FOOTGUN: combined with `retry` (CreateMailOpts), a retry against a
     * non-supporting provider (postmark/ses) can double-send. For those, dedup the
     * send-job one layer up via jobs' IdempotencyStore at dispatch — mail does not
     * own cross-provider exactly-once.
     */
    idempotencyKey?: string;
};
type MailResult = {
    id: string;
    provider: string;
};
type MailAdapter = {
    send(msg: MailMessage): Promise<MailResult>;
};
declare class MailError extends Error {
}
declare class MailValidationError extends MailError {
    readonly field?: string | undefined;
    readonly name = "MailValidationError";
    constructor(message: string, field?: string | undefined);
}
declare class MailProviderError extends MailError {
    readonly provider: string;
    readonly retryable: boolean;
    readonly cause?: unknown | undefined;
    readonly name = "MailProviderError";
    constructor(message: string, provider: string, retryable?: boolean, cause?: unknown | undefined);
}
/** Trust-boundary floor — malformed addresses never reach an adapter. */
declare function validateMessage(msg: MailMessage): void;
type CreateMailOpts = {
    /**
     * Invoked around adapter.send — host decides retry policy for transient failures.
     *
     * FOOTGUN: a retry against a provider with no native idempotency (postmark/ses)
     * can double-send. `idempotencyKey` only protects providers that support it
     * (resend/brevo). For the rest, dedup the send-job one layer up via jobs'
     * IdempotencyStore at dispatch — mail does not own cross-provider exactly-once.
     */
    retry?: (attempt: () => Promise<MailResult>) => Promise<MailResult>;
};
declare function createMail(adapter: MailAdapter, opts?: CreateMailOpts): {
    send(msg: MailMessage): Promise<MailResult>;
};
/** Zero-dep `{{key}}` interpolation — missing keys become empty strings. */
declare function render(tpl: string, data: Record<string, string>): string;

export { type CreateMailOpts, type MailAdapter, MailError, type MailMessage, MailProviderError, type MailResult, MailValidationError, type SuppressionEvent, createMail, render, validateMessage };
