diff --git a/types/nodemailer/.npmignore b/types/nodemailer/.npmignore index 5ff4b74c22c0a6..4a3c59fbf46115 100644 --- a/types/nodemailer/.npmignore +++ b/types/nodemailer/.npmignore @@ -4,3 +4,4 @@ !**/*.d.mts !**/*.d.*.ts /v6/ +/v7/ diff --git a/types/nodemailer/package.json b/types/nodemailer/package.json index 9774a4ca4d8a55..ce77d5cc12f417 100644 --- a/types/nodemailer/package.json +++ b/types/nodemailer/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@types/nodemailer", - "version": "7.0.9999", + "version": "8.0.9999", "projects": [ "https://github.com/nodemailer/nodemailer", "https://nodemailer.com" diff --git a/types/nodemailer/v7/.npmignore b/types/nodemailer/v7/.npmignore new file mode 100644 index 00000000000000..93e307400a5456 --- /dev/null +++ b/types/nodemailer/v7/.npmignore @@ -0,0 +1,5 @@ +* +!**/*.d.ts +!**/*.d.cts +!**/*.d.mts +!**/*.d.*.ts diff --git a/types/nodemailer/v7/index.d.ts b/types/nodemailer/v7/index.d.ts new file mode 100644 index 00000000000000..c03c50ffdbe011 --- /dev/null +++ b/types/nodemailer/v7/index.d.ts @@ -0,0 +1,82 @@ +/// + +import JSONTransport = require("./lib/json-transport"); +import Mail = require("./lib/mailer"); +import MailMessage = require("./lib/mailer/mail-message"); +import SendmailTransport = require("./lib/sendmail-transport"); +import SESTransport = require("./lib/ses-transport"); +import SMTPPool = require("./lib/smtp-pool"); +import SMTPTransport = require("./lib/smtp-transport"); +import StreamTransport = require("./lib/stream-transport"); + +export type SendMailOptions = Mail.Options; + +export type Transporter = Mail; + +export type SentMessageInfo = any; + +export interface Transport { + mailer?: Transporter | undefined; + + name: string; + version: string; + + send(mail: MailMessage, callback: (err: Error | null, info: T) => void): void; + + verify?(callback: (err: Error | null, success: true) => void): void; + verify?(): Promise; + + close?(): void; +} + +export interface TransportOptions { + component?: string | undefined; +} + +export interface TestAccount { + user: string; + pass: string; + smtp: { host: string; port: number; secure: boolean }; + imap: { host: string; port: number; secure: boolean }; + pop3: { host: string; port: number; secure: boolean }; + web: string; +} + +export function createTransport( + transport: SMTPPool | SMTPPool.Options, + defaults?: SMTPPool.Options, +): Transporter; +export function createTransport( + transport: SendmailTransport | SendmailTransport.Options, + defaults?: SendmailTransport.Options, +): Transporter; +export function createTransport( + transport: StreamTransport | StreamTransport.Options, + defaults?: StreamTransport.Options, +): Transporter; +export function createTransport( + transport: JSONTransport | JSONTransport.Options, + defaults?: JSONTransport.Options, +): Transporter; +export function createTransport( + transport: SESTransport | SESTransport.Options, + defaults?: SESTransport.Options, +): Transporter; +export function createTransport( + transport?: SMTPTransport | SMTPTransport.Options | string, + defaults?: SMTPTransport.Options, +): Transporter; +// eslint-disable-next-line @definitelytyped/no-unnecessary-generics +export function createTransport( + transport: Transport | TransportOptions, + defaults?: TransportOptions, +): Transporter; + +export function createTestAccount( + apiUrl: string, + callback: (err: Error | null, testAccount: TestAccount) => void, +): void; +export function createTestAccount(callback: (err: Error | null, testAccount: TestAccount) => void): void; +export function createTestAccount(apiUrl?: string): Promise; + +export function getTestMessageUrl(info: SESTransport.SentMessageInfo | SMTPTransport.SentMessageInfo): string | false; diff --git a/types/nodemailer/v7/lib/addressparser/index.d.ts b/types/nodemailer/v7/lib/addressparser/index.d.ts new file mode 100644 index 00000000000000..e65d471f95491a --- /dev/null +++ b/types/nodemailer/v7/lib/addressparser/index.d.ts @@ -0,0 +1,31 @@ +declare namespace addressparser { + interface Address { + name: string; + address: string; + } + + interface Group { + name: string; + group: Address[]; + } + + type AddressOrGroup = Address | Group; +} + +/** + * Parses structured e-mail addresses from an address field + * + * Example: + * + * 'Name ' + * + * will be converted to + * + * [{name: 'Name', address: 'address@domain'}] + * + * @return An array of address objects + */ +declare function addressparser(address: string, options: { flatten: true }): addressparser.Address[]; +declare function addressparser(address: string, options?: { flatten: false }): addressparser.AddressOrGroup[]; + +export = addressparser; diff --git a/types/nodemailer/v7/lib/base64/index.d.ts b/types/nodemailer/v7/lib/base64/index.d.ts new file mode 100644 index 00000000000000..26368c8ee355de --- /dev/null +++ b/types/nodemailer/v7/lib/base64/index.d.ts @@ -0,0 +1,22 @@ +/// + +import { Transform, TransformOptions } from "stream"; + +/** Encodes a Buffer into a base64 encoded string */ +export function encode(buffer: Buffer | string): string; + +/** Adds soft line breaks to a base64 string */ +export function wrap(str: string, lineLength?: number): string; + +export interface EncoderOptions extends TransformOptions { + lineLength?: number | false | undefined; +} + +export class Encoder extends Transform { + options: EncoderOptions; + + inputBytes: number; + outputBytes: number; + + constructor(options?: EncoderOptions); +} diff --git a/types/nodemailer/v7/lib/dkim/index.d.ts b/types/nodemailer/v7/lib/dkim/index.d.ts new file mode 100644 index 00000000000000..98bd34e47b1582 --- /dev/null +++ b/types/nodemailer/v7/lib/dkim/index.d.ts @@ -0,0 +1,45 @@ +/// + +import { PassThrough, Readable } from "stream"; + +declare namespace DKIM { + interface OptionalOptions { + /** optional location for cached messages. If not set then caching is not used. */ + cacheDir?: string | false | undefined; + /** optional size in bytes, if message is larger than this treshold it gets cached to disk (assuming cacheDir is set and writable). Defaults to 131072 (128 kB). */ + cacheTreshold?: number | undefined; + /** optional algorithm for the body hash, defaults to ‘sha256’ */ + hashAlgo?: string | undefined; + /** an optional colon separated list of header keys to sign (eg. message-id:date:from:to...') */ + headerFieldNames?: string | undefined; + /** optional colon separated list of header keys not to sign. This is useful if you want to sign all the relevant keys but your provider changes some values, ie Message-ID and Date. In this case you should use 'message-id:date' to prevent signing these values. */ + skipFields?: string | undefined; + } + + interface SingleKeyOptions extends OptionalOptions { + /** is the domain name to use in the signature */ + domainName: string; + /** is the DKIM key selector */ + keySelector: string; + /** is the private key for the selector in PEM format */ + privateKey: string | { key: string; passphrase: string }; + } + + interface MultipleKeysOptions extends OptionalOptions { + /** is an optional array of key objects (domainName, keySelector, privateKey) if you want to add more than one signature to the message. If this value is set then the default key values are ignored */ + keys: SingleKeyOptions[]; + } + + type Options = SingleKeyOptions | MultipleKeysOptions; +} + +declare class DKIM { + options: DKIM.Options; + keys: DKIM.SingleKeyOptions[]; + + constructor(options?: DKIM.Options); + + sign(input: string | Buffer | Readable, extraOptions?: DKIM.Options): PassThrough; +} + +export = DKIM; diff --git a/types/nodemailer/v7/lib/dkim/message-parser.d.ts b/types/nodemailer/v7/lib/dkim/message-parser.d.ts new file mode 100644 index 00000000000000..a8f39b77df19e5 --- /dev/null +++ b/types/nodemailer/v7/lib/dkim/message-parser.d.ts @@ -0,0 +1,75 @@ +/// + +import { Transform } from "stream"; + +declare namespace MessageParser { + interface Header { + key: string; + line: string; + } +} + +/** + * MessageParser instance is a transform stream that separates message headers + * from the rest of the body. Headers are emitted with the 'headers' event. Message + * body is passed on as the resulting stream. + */ +declare class MessageParser extends Transform { + addListener(event: "headers", listener: (headers: MessageParser.Header[]) => void): this; + addListener(event: "close", listener: () => void): this; + addListener(event: "data", listener: (chunk: any) => void): this; + addListener(event: "end", listener: () => void): this; + addListener(event: "readable", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + + emit(event: "headers", headers: MessageParser.Header[]): boolean; + emit(event: "close"): boolean; + emit(event: "data", chunk: any): boolean; + emit(event: "end"): boolean; + emit(event: "readable"): boolean; + emit(event: "error", err: Error): boolean; + emit(event: string | symbol, ...args: any[]): boolean; + + on(event: "headers", listener: (headers: MessageParser.Header[]) => void): this; + on(event: "close", listener: () => void): this; + on(event: "data", listener: (chunk: any) => void): this; + on(event: "end", listener: () => void): this; + on(event: "readable", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + + once(event: "headers", listener: (headers: MessageParser.Header[]) => void): this; + once(event: "close", listener: () => void): this; + once(event: "data", listener: (chunk: any) => void): this; + once(event: "end", listener: () => void): this; + once(event: "readable", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + + prependListener(event: "headers", listener: (headers: MessageParser.Header[]) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener(event: "data", listener: (chunk: any) => void): this; + prependListener(event: "end", listener: () => void): this; + prependListener(event: "readable", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: string | symbol, listener: (...args: any[]) => void): this; + + prependOnceListener(event: "headers", listener: (headers: MessageParser.Header[]) => void): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "data", listener: (chunk: any) => void): this; + prependOnceListener(event: "end", listener: () => void): this; + prependOnceListener(event: "readable", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this; + + removeListener(event: "headers", listener: (headers: MessageParser.Header[]) => void): this; + removeListener(event: "close", listener: () => void): this; + removeListener(event: "data", listener: (chunk: any) => void): this; + removeListener(event: "end", listener: () => void): this; + removeListener(event: "readable", listener: () => void): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: string | symbol, listener: (...args: any[]) => void): this; +} + +export = MessageParser; diff --git a/types/nodemailer/v7/lib/dkim/relaxed-body.d.ts b/types/nodemailer/v7/lib/dkim/relaxed-body.d.ts new file mode 100644 index 00000000000000..408ddc1d5f977d --- /dev/null +++ b/types/nodemailer/v7/lib/dkim/relaxed-body.d.ts @@ -0,0 +1,75 @@ +/// + +import { Transform, TransformOptions } from "stream"; + +declare namespace RelaxedBody { + interface Options extends TransformOptions { + hashAlgo?: string; + debug?: boolean; + } +} + +/** + * Streams through a message body and calculates relaxed body hash + */ +declare class RelaxedBody extends Transform { + constructor(options?: RelaxedBody.Options); + + addListener(event: "hash", listener: (digest: Buffer, debugBody: Buffer | false) => void): this; + addListener(event: "close", listener: () => void): this; + addListener(event: "data", listener: (chunk: any) => void): this; + addListener(event: "end", listener: () => void): this; + addListener(event: "readable", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + + emit(event: "hash", digest: Buffer, debugBody: Buffer | false): boolean; + emit(event: "close"): boolean; + emit(event: "data", chunk: any): boolean; + emit(event: "end"): boolean; + emit(event: "readable"): boolean; + emit(event: "error", err: Error): boolean; + emit(event: string | symbol, ...args: any[]): boolean; + + on(event: "hash", listener: (digest: Buffer, debugBody: Buffer | false) => void): this; + on(event: "close", listener: () => void): this; + on(event: "data", listener: (chunk: any) => void): this; + on(event: "end", listener: () => void): this; + on(event: "readable", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + + once(event: "hash", listener: (digest: Buffer, debugBody: Buffer | false) => void): this; + once(event: "close", listener: () => void): this; + once(event: "data", listener: (chunk: any) => void): this; + once(event: "end", listener: () => void): this; + once(event: "readable", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + + prependListener(event: "hash", listener: (digest: Buffer, debugBody: Buffer | false) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener(event: "data", listener: (chunk: any) => void): this; + prependListener(event: "end", listener: () => void): this; + prependListener(event: "readable", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: string | symbol, listener: (...args: any[]) => void): this; + + prependOnceListener(event: "hash", listener: (digest: Buffer, debugBody: Buffer | false) => void): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "data", listener: (chunk: any) => void): this; + prependOnceListener(event: "end", listener: () => void): this; + prependOnceListener(event: "readable", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this; + + removeListener(event: "hash", listener: (digest: Buffer, debugBody: Buffer | false) => void): this; + removeListener(event: "close", listener: () => void): this; + removeListener(event: "data", listener: (chunk: any) => void): this; + removeListener(event: "end", listener: () => void): this; + removeListener(event: "readable", listener: () => void): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: string | symbol, listener: (...args: any[]) => void): this; +} + +export = RelaxedBody; diff --git a/types/nodemailer/v7/lib/dkim/sign.d.ts b/types/nodemailer/v7/lib/dkim/sign.d.ts new file mode 100644 index 00000000000000..2b47c35bf2a741 --- /dev/null +++ b/types/nodemailer/v7/lib/dkim/sign.d.ts @@ -0,0 +1,21 @@ +import DKIM = require("."); +import MessageParser = require("./message-parser"); + +/** Returns DKIM signature header line */ +declare function relaxedHeaders( + headers: MessageParser.Header[], + hashAlgo: string, + bodyHash: string, + options?: DKIM.SingleKeyOptions, +): string; + +declare namespace relaxedHeaders { + function relaxedHeaders( + headers: MessageParser.Header[], + hashAlgo: string, + bodyHash: string, + options?: DKIM.SingleKeyOptions, + ): string; +} + +export = relaxedHeaders; diff --git a/types/nodemailer/v7/lib/fetch/cookies.d.ts b/types/nodemailer/v7/lib/fetch/cookies.d.ts new file mode 100644 index 00000000000000..d273fa04d185ea --- /dev/null +++ b/types/nodemailer/v7/lib/fetch/cookies.d.ts @@ -0,0 +1,54 @@ +type s = number; + +declare namespace Cookies { + interface Cookie { + name: string; + value?: string | undefined; + expires?: Date | undefined; + path?: string | undefined; + domain?: string | undefined; + secure?: boolean | undefined; + httponly?: boolean | undefined; + } + + interface Options { + sessionTimeout?: s | undefined; + } +} + +/** Creates a biskviit cookie jar for managing cookie values in memory */ +declare class Cookies { + options: Cookies.Options; + cookies: Cookies.Cookie[]; + + constructor(options?: Cookies.Options); + + /** Stores a cookie string to the cookie storage */ + set(cookieStr: string, url: string): boolean; + + /** Returns cookie string for the 'Cookie:' header. */ + get(url: string): string; + + /** Lists all valied cookie objects for the specified URL */ + list(url: string): Cookies.Cookie[]; + + /** Parses cookie string from the 'Set-Cookie:' header */ + parse(cookieStr: string): Cookies.Cookie; + + /** Checks if a cookie object is valid for a specified URL */ + match(cookie: Cookies.Cookie, url: string): boolean; + + /** Adds (or updates/removes if needed) a cookie object to the cookie storage */ + add(cookie: Cookies.Cookie): boolean; + + /** Checks if two cookie objects are the same */ + compare(a: Cookies.Cookie, b: Cookies.Cookie): boolean; + + /** Checks if a cookie is expired */ + isExpired(cookie: Cookies.Cookie): boolean; + + /** Returns normalized cookie path for an URL path argument */ + getPath(pathname: string): string; +} + +export = Cookies; diff --git a/types/nodemailer/v7/lib/fetch/index.d.ts b/types/nodemailer/v7/lib/fetch/index.d.ts new file mode 100644 index 00000000000000..b85425b682b02f --- /dev/null +++ b/types/nodemailer/v7/lib/fetch/index.d.ts @@ -0,0 +1,38 @@ +/// + +type ms = number; + +import _Cookies = require("./cookies"); + +import * as http from "http"; +import { Writable } from "stream"; +import * as tls from "tls"; + +declare namespace fetch { + type Cookies = _Cookies; + + interface WritableResponse extends Writable { + statusCode: number; + headers: http.IncomingHttpHeaders; + } + + interface Options { + fetchRes?: Writable | undefined; + cookies?: Cookies | undefined; + cookie?: string | undefined; + redirects?: number | undefined; + maxRedirects?: number | undefined; + method?: string | undefined; + headers?: { [key: string]: string } | undefined; + userAgent?: string | undefined; + body?: Buffer | string | { [key: string]: string } | undefined; + contentType?: string | false | undefined; + tls?: tls.TlsOptions | undefined; + timeout?: ms | undefined; + allowErrorResponse?: boolean | undefined; + } +} + +declare function fetch(url: string, options?: fetch.Options): fetch.WritableResponse; + +export = fetch; diff --git a/types/nodemailer/v7/lib/json-transport/index.d.ts b/types/nodemailer/v7/lib/json-transport/index.d.ts new file mode 100644 index 00000000000000..f128a2879c55bd --- /dev/null +++ b/types/nodemailer/v7/lib/json-transport/index.d.ts @@ -0,0 +1,53 @@ +/// + +import { EventEmitter } from "events"; + +import { Transport, TransportOptions } from "../.."; + +import * as shared from "../shared"; + +import Mail = require("../mailer"); +import MailMessage = require("../mailer/mail-message"); +import MimeNode = require("../mime-node"); + +declare namespace JSONTransport { + type MailOptions = Mail.Options; + + interface Options extends MailOptions, TransportOptions { + jsonTransport: true; + skipEncoding?: boolean | undefined; + } + + interface SentMessageInfo { + /** an envelope object {from:‘address’, to:[‘address’]} */ + envelope: MimeNode.Envelope; + /** the Message-ID header value */ + messageId: string; + /** JSON string */ + message: string; + accepted: Array; + rejected: Array; + pending: Array; + response: string; + } +} + +declare class JSONTransport implements Transport { + options: JSONTransport.Options; + + logger: shared.Logger; + mailer: Mail; + + name: string; + version: string; + + constructor(options: JSONTransport.Options); + + /** Compiles a mailcomposer message and forwards it to handler that sends it */ + send( + mail: MailMessage, + callback: (err: Error | null, info: JSONTransport.SentMessageInfo) => void, + ): void; +} + +export = JSONTransport; diff --git a/types/nodemailer/v7/lib/mail-composer/index.d.ts b/types/nodemailer/v7/lib/mail-composer/index.d.ts new file mode 100644 index 00000000000000..6d8357574d5d8c --- /dev/null +++ b/types/nodemailer/v7/lib/mail-composer/index.d.ts @@ -0,0 +1,25 @@ +/// + +import { URL } from "url"; + +import Mail = require("../mailer"); +import MimeNode = require("../mime-node"); + +/** Creates the object for composing a MimeNode instance out from the mail options */ +declare class MailComposer { + mail: Mail.Options; + message: MimeNode | false; + + constructor(mail: Mail.Options); + + /** Builds MimeNode instance */ + compile(): MimeNode; + + /** List all attachments. Resulting attachment objects can be used as input for MimeNode nodes */ + getAttachments(findRelated: boolean): Mail.Attachment[]; + + /** List alternatives. Resulting objects can be used as input for MimeNode nodes */ + getAlternatives(): Mail.Attachment[]; +} + +export = MailComposer; diff --git a/types/nodemailer/v7/lib/mailer/index.d.ts b/types/nodemailer/v7/lib/mailer/index.d.ts new file mode 100644 index 00000000000000..38e6e71c4d9f10 --- /dev/null +++ b/types/nodemailer/v7/lib/mailer/index.d.ts @@ -0,0 +1,281 @@ +/// + +import { EventEmitter } from "events"; +import { Socket } from "net"; +import { Readable } from "stream"; +import { Url } from "url"; + +import { Transport, TransportOptions } from "../.."; +import * as shared from "../shared"; + +import DKIM = require("../dkim"); +import MailMessage = require("./mail-message"); +import MimeNode = require("../mime-node"); +import SMTPConnection = require("../smtp-connection"); +import XOAuth2 = require("../xoauth2"); + +declare namespace Mail { + type Headers = + | { [key: string]: string | string[] | { prepared: boolean; value: string } } + | Array<{ key: string; value: string }>; + + type ListHeader = string | { url: string; comment: string }; + + interface ListHeaders { + [key: string]: ListHeader | ListHeader[] | ListHeader[][]; + } + + type TextEncoding = "quoted-printable" | "base64"; + + interface Address { + name: string; + address: string; + } + + interface AttachmentLike { + /** String, Buffer or a Stream contents for the attachment */ + content?: string | Buffer | Readable | undefined; + /** path to a file or an URL (data uris are allowed as well) if you want to stream the file instead of including it (better for larger attachments) */ + path?: string | Url | undefined; + } + + interface Attachment extends AttachmentLike { + /** filename to be reported as the name of the attached file, use of unicode is allowed. If you do not want to use a filename, set this value as false, otherwise a filename is generated automatically */ + filename?: string | false | undefined; + /** optional content id for using inline images in HTML message source. Using cid sets the default contentDisposition to 'inline' and moves the attachment into a multipart/related mime node, so use it only if you actually want to use this attachment as an embedded image */ + cid?: string | undefined; + /** If set and content is string, then encodes the content to a Buffer using the specified encoding. Example values: base64, hex, binary etc. Useful if you want to use binary attachments in a JSON formatted e-mail object */ + encoding?: string | undefined; + /** optional content type for the attachment, if not set will be derived from the filename property */ + contentType?: string | undefined; + /** optional transfer encoding for the attachment, if not set it will be derived from the contentType property. Example values: quoted-printable, base64. If it is unset then base64 encoding is used for the attachment. If it is set to false then previous default applies (base64 for most, 7bit for text). */ + contentTransferEncoding?: "7bit" | "base64" | "quoted-printable" | false | undefined; + /** optional content disposition type for the attachment, defaults to ‘attachment’ */ + contentDisposition?: "attachment" | "inline" | undefined; + /** is an object of additional headers */ + headers?: Headers | undefined; + /** an optional value that overrides entire node content in the mime message. If used then all other options set for this node are ignored. */ + raw?: string | Buffer | Readable | AttachmentLike | undefined; + } + + interface AmpAttachment extends AttachmentLike { + /** is an alternative for content to load the AMP4EMAIL data from an URL */ + href?: string | undefined; + /** defines optional content encoding, eg. ‘base64’ or ‘hex’. This only applies if the content is a string. By default an unicode string is assumed. */ + encoding?: string | undefined; + /** optional content type for the attachment, if not set will be derived from the filename property */ + contentType?: string | undefined; + /** an optional value that overrides entire node content in the mime message. If used then all other options set for this node are ignored. */ + raw?: string | Buffer | Readable | AttachmentLike | undefined; + } + + interface IcalAttachment extends AttachmentLike { + /** optional method, case insensitive, defaults to ‘publish’. Other possible values would be ‘request’, ‘reply’, ‘cancel’ or any other valid calendar method listed in RFC5546. This should match the METHOD: value in calendar event file. */ + method?: string | undefined; + /** optional filename, defaults to ‘invite.ics’ */ + filename?: string | false | undefined; + /** is an alternative for content to load the calendar data from an URL */ + href?: string | undefined; + /** defines optional content encoding, eg. ‘base64’ or ‘hex’. This only applies if the content is a string. By default an unicode string is assumed. */ + encoding?: string | undefined; + } + + interface Connection { + connection: Socket; + } + + interface Envelope { + /** the first address gets used as MAIL FROM address in SMTP */ + from?: string | undefined; + /** addresses from this value get added to RCPT TO list */ + to?: string | undefined; + /** addresses from this value get added to RCPT TO list */ + cc?: string | undefined; + /** addresses from this value get added to RCPT TO list */ + bcc?: string | undefined; + } + + interface Options { + /** The e-mail address of the sender. All e-mail addresses can be plain 'sender@server.com' or formatted 'Sender Name ' */ + from?: string | Address | Array | undefined; + /** An e-mail address that will appear on the Sender: field */ + sender?: string | Address | undefined; + /** Comma separated list or an array of recipients e-mail addresses that will appear on the To: field */ + to?: string | Address | Array | undefined; + /** Comma separated list or an array of recipients e-mail addresses that will appear on the Cc: field */ + cc?: string | Address | Array | undefined; + /** Comma separated list or an array of recipients e-mail addresses that will appear on the Bcc: field */ + bcc?: string | Address | Array | undefined; + /** Comma separated list or an array of e-mail addresses that will appear on the Reply-To: field */ + replyTo?: string | Address | Array | undefined; + /** The message-id this message is replying */ + inReplyTo?: string | Address | undefined; + /** Message-id list (an array or space separated string) */ + references?: string | string[] | undefined; + /** The subject of the e-mail */ + subject?: string | undefined; + /** The plaintext version of the message */ + text?: string | Buffer | Readable | AttachmentLike | undefined; + /** The HTML version of the message */ + html?: string | Buffer | Readable | AttachmentLike | undefined; + /** Apple Watch specific HTML version of the message, same usage as with text and html */ + watchHtml?: string | Buffer | Readable | AttachmentLike | undefined; + /** AMP4EMAIL specific HTML version of the message, same usage as with text and html. Make sure it is a full and valid AMP4EMAIL document, otherwise the displaying email client falls back to html and ignores the amp part */ + amp?: string | Buffer | Readable | AmpAttachment | undefined; + /** iCalendar event, same usage as with text and html. Event method attribute defaults to ‘PUBLISH’ or define it yourself: {method: 'REQUEST', content: iCalString}. This value is added as an additional alternative to html or text. Only utf-8 content is allowed */ + icalEvent?: string | Buffer | Readable | IcalAttachment | undefined; + /** An object or array of additional header fields */ + headers?: Headers | undefined; + /** An object where key names are converted into list headers. List key help becomes List-Help header etc. */ + list?: ListHeaders | undefined; + /** An array of attachment objects */ + attachments?: Attachment[] | undefined; + /** An array of alternative text contents (in addition to text and html parts) */ + alternatives?: Attachment[] | undefined; + /** optional SMTP envelope, if auto generated envelope is not suitable */ + envelope?: Envelope | MimeNode.Envelope | undefined; + /** optional Message-Id value, random value will be generated if not set */ + messageId?: string | undefined; + /** optional Date value, current UTC string will be used if not set */ + date?: Date | string | undefined; + /** optional transfer encoding for the textual parts */ + encoding?: string | undefined; + /** if set then overwrites entire message output with this value. The value is not parsed, so you should still set address headers or the envelope value for the message to work */ + raw?: string | Buffer | Readable | AttachmentLike | undefined; + /** set explicitly which encoding to use for text parts (quoted-printable or base64). If not set then encoding is detected from text content (mostly ascii means quoted-printable, otherwise base64) */ + textEncoding?: TextEncoding | undefined; + /** if set to true then fails with an error when a node tries to load content from URL */ + disableUrlAccess?: boolean | undefined; + /** if set to true then fails with an error when a node tries to load content from a file */ + disableFileAccess?: boolean | undefined; + /** is an object with DKIM options */ + dkim?: DKIM.Options | undefined; + /** method to normalize header keys for custom caseing */ + normalizeHeaderKey?(key: string): string; + priority?: "high" | "normal" | "low" | undefined; + /** if set to true then converts data:images in the HTML content of message to embedded attachments */ + attachDataUrls?: boolean | undefined; + /** if set to false then removes x-mailer header, otherwise replaces the default x-mailer header value **/ + xMailer?: false | string; + } + + type PluginFunction = (mail: MailMessage, callback: (err?: Error | null) => void) => void; +} + +/** Creates an object for exposing the Mail API */ +declare class Mail extends EventEmitter { + options: Mail.Options; + meta: Map; + dkim: DKIM; + transporter: Transport; + logger: shared.Logger; + + /** Usage: typeof transporter.MailMessage */ + MailMessage: MailMessage; + + _defaults: DefaultTransportOptions; + + constructor(transporter: Transport, options?: TransportOptions, defaults?: DefaultTransportOptions); + + /** Closes all connections in the pool. If there is a message being sent, the connection is closed later */ + close(): void; + + /** Returns true if there are free slots in the queue */ + isIdle(): boolean; + + /** Verifies SMTP configuration */ + verify(callback: (err: Error | null, success: true) => void): void; + verify(): Promise; + + use(step: string, plugin: Mail.PluginFunction): this; + + /** Sends an email using the preselected transport object */ + sendMail( + mailOptions: Mail.Options & Partial, + callback: (err: Error | null, info: T) => void, + ): void; + sendMail(mailOptions: Mail.Options, callback: (err: Error | null, info: T) => void): void; + sendMail(mailOptions: Mail.Options & Partial): Promise; + sendMail(mailOptions: Mail.Options): Promise; + + getVersionString(): string; + + /** Sets up proxy handler for a Nodemailer object */ + setupProxy(proxyUrl: string): void; + + set( + key: "oauth2_provision_cb", + value: ( + user: string, + renew: boolean, + callback: (err: Error | null, accessToken?: string, expires?: number) => void, + ) => void, + ): Map; + set( + key: + | "proxy_handler_http" + | "proxy_handler_https" + | "proxy_handler_socks" + | "proxy_handler_socks5" + | "proxy_handler_socks4" + | "proxy_handler_socks4a", + value: ( + proxy: Url, + options: TransportOptions, + callback: (err: Error | null, socketOptions?: { connection: Socket }) => void, + ) => void, + ): Map; + set(key: string, value: any): Map; + + get( + key: "oauth2_provision_cb", + ): ( + user: string, + renew: boolean, + callback: (err: Error | null, accessToken: string, expires: number) => void, + ) => void; + get( + key: + | "proxy_handler_http" + | "proxy_handler_https" + | "proxy_handler_socks" + | "proxy_handler_socks5" + | "proxy_handler_socks4" + | "proxy_handler_socks4a", + ): ( + proxy: Url, + options: TransportOptions, + callback: (err: Error | null, socketOptions: { connection: Socket }) => void, + ) => void; + get(key: string): any; + + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "idle", listener: () => void): this; + addListener(event: "token", listener: (token: XOAuth2.Token) => void): this; + + emit(event: "error", error: Error): boolean; + emit(event: "idle"): boolean; + emit(event: "token", token: XOAuth2.Token): boolean; + + on(event: "error", listener: (err: Error) => void): this; + on(event: "idle", listener: () => void): this; + on(event: "token", listener: (token: XOAuth2.Token) => void): this; + + once(event: "error", listener: (err: Error) => void): this; + once(event: "idle", listener: () => void): this; + once(event: "token", listener: (token: XOAuth2.Token) => void): this; + + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "idle", listener: () => void): this; + prependListener(event: "end", listener: (token: XOAuth2.Token) => void): this; + + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "idle", listener: () => void): this; + prependOnceListener(event: "end", listener: (token: XOAuth2.Token) => void): this; + + listeners(event: "error"): Array<(err: Error) => void>; + listeners(event: "idle"): Array<() => void>; + listeners(event: "end"): Array<(token: XOAuth2.Token) => void>; +} + +export = Mail; diff --git a/types/nodemailer/v7/lib/mailer/mail-message.d.ts b/types/nodemailer/v7/lib/mailer/mail-message.d.ts new file mode 100644 index 00000000000000..fe7fdb9e9a60ef --- /dev/null +++ b/types/nodemailer/v7/lib/mailer/mail-message.d.ts @@ -0,0 +1,32 @@ +/// + +import { Readable } from "stream"; + +import Mail = require("."); +import MimeNode = require("../mime-node"); + +declare class MailMessage { + mailer: Mail; + data: Mail.Options; + message: MimeNode; + + constructor(mailer: Mail, data: Mail.Options); + + resolveContent( + data: object | any[], + key: string | number, + callback: (err: Error | null, value?: any) => any, + ): Promise; + + resolveAll(callback: (err?: Error | null, data?: Mail.Options) => void): void; + + normalize(callback: (err?: Error | null, data?: Mail.Options) => void): void; + + setMailerHeader(): void; + + setPriorityHeaders(): void; + + setListHeaders(): void; +} + +export = MailMessage; diff --git a/types/nodemailer/v7/lib/mime-funcs/index.d.ts b/types/nodemailer/v7/lib/mime-funcs/index.d.ts new file mode 100644 index 00000000000000..53969d826dafea --- /dev/null +++ b/types/nodemailer/v7/lib/mime-funcs/index.d.ts @@ -0,0 +1,87 @@ +export interface HeaderValue { + value: string; + params?: { [key: string]: string } | undefined; +} + +export interface ParsedHeaderValue extends HeaderValue { + params: { [key: string]: string }; +} + +export interface ParsedHeaderParam { + key: string; + value: string; +} + +/** Checks if a value is plaintext string (uses only printable 7bit chars) */ +export function isPlainText(value: string): boolean; + +/** + * Checks if a multi line string containes lines longer than the selected value. + * + * Useful when detecting if a mail message needs any processing at all – + * if only plaintext characters are used and lines are short, then there is + * no need to encode the values in any way. If the value is plaintext but has + * longer lines then allowed, then use format=flowed + */ +export function hasLongerLines(str: string, lineLength: number): boolean; + +/** Encodes a string or an Buffer to an UTF-8 MIME Word (rfc2047) */ +export function encodeWord(data: Buffer | string, mimeWordEncoding?: "Q" | "B", maxLength?: number): string; + +/** Finds word sequences with non ascii text and converts these to mime words */ +export function encodeWords(value: string, mimeWordEncoding?: "Q" | "B", maxLength?: number): string; + +/** + * Joins parsed header value together as 'value; param1=value1; param2=value2' + * PS: We are following RFC 822 for the list of special characters that we need to keep in quotes. + * Refer: https://www.w3.org/Protocols/rfc1341/4_Content-Type.html + */ +export function buildHeaderValue(structured: HeaderValue): string; + +/** + * Encodes a string or an Buffer to an UTF-8 Parameter Value Continuation encoding (rfc2231) + * Useful for splitting long parameter values. + * + * For example + * ``` + * title="unicode string" + * ``` + * becomes + * ``` + * title*0*=utf-8''unicode + * title*1*=%20string + * ``` + */ +export function buildHeaderParam(key: string, data: Buffer | string, maxLength?: number): ParsedHeaderParam[]; + +/** + * Parses a header value with key=value arguments into a structured + * object. + * + * ``` + * parseHeaderValue('content-type: text/plain; CHARSET='UTF-8') -> + * { + * 'value': 'text/plain', + * 'params': { + * 'charset': 'UTF-8' + * } + * } + * ``` + */ +export function parseHeaderValue(str: string): ParsedHeaderValue; + +/** Returns file extension for a content type string. If no suitable extensions are found, 'bin' is used as the default extension */ +export function detectExtension(mimeType: string): string; + +/** Returns content type for a file extension. If no suitable content types are found, 'application/octet-stream' is used as the default content type */ +export function detectMimeType(extension: string): string; + +/** Folds long lines, useful for folding header lines (afterSpace=false) and flowed text (afterSpace=true) */ +export function foldLines(str: string, lineLength?: number, afterSpace?: boolean): string; + +/** Splits a mime encoded string. Needed for dividing mime words into smaller chunks */ +export function splitMimeEncodedString(str: string, maxlen?: number): string[]; + +export function encodeURICharComponent(chr: string): string; + +export function safeEncodeURIComponent(str: string): string; diff --git a/types/nodemailer/v7/lib/mime-funcs/mime-types.d.ts b/types/nodemailer/v7/lib/mime-funcs/mime-types.d.ts new file mode 100644 index 00000000000000..7b9f9c529c4d48 --- /dev/null +++ b/types/nodemailer/v7/lib/mime-funcs/mime-types.d.ts @@ -0,0 +1,2 @@ +export function detectMimeType(filename: string | false): string; +export function detectExtension(mimeType: string | false): string; diff --git a/types/nodemailer/v7/lib/mime-node/index.d.ts b/types/nodemailer/v7/lib/mime-node/index.d.ts new file mode 100644 index 00000000000000..ac993b47d5f8d5 --- /dev/null +++ b/types/nodemailer/v7/lib/mime-node/index.d.ts @@ -0,0 +1,224 @@ +/// + +import { Readable, ReadableOptions, Transform } from "stream"; + +import Mail = require("../mailer"); + +declare namespace MimeNode { + interface Addresses { + from?: string[] | undefined; + sender?: string[] | undefined; + "reply-to"?: string[] | undefined; + to?: string[] | undefined; + cc?: string[] | undefined; + bcc?: string[] | undefined; + } + + interface Envelope { + /** includes an address object or is set to false */ + from: string | false; + /** includes an array of address objects */ + to: string[]; + } + + interface Options { + /** root node for this tree */ + rootNode?: MimeNode | undefined; + + /** immediate parent for this node */ + parentNode?: MimeNode | undefined; + + /** filename for an attachment node */ + filename?: string | undefined; + + /** Hostname for default message-id values */ + hostname?: string | undefined; + + /** shared part of the unique multipart boundary */ + baseBoundary?: string | undefined; + + /** If true, do not exclude Bcc from the generated headers */ + keepBcc?: boolean | undefined; + + /** + * If set to 'win' then uses \r\n, + * if 'linux' then \n. + * If not set (or `raw` is used) then newlines are kept as is. + */ + newline?: string | undefined; + + /** either 'Q' (the default) or 'B' */ + textEncoding?: "B" | "Q" | undefined; + + /** method to normalize header keys for custom caseing */ + normalizeHeaderKey?(key: string): string; + + /** undocumented */ + boundaryPrefix?: string | undefined; + + /** Undocumented */ + disableFileAccess?: boolean | undefined; + + /** Undocumented */ + disableUrlAccess?: boolean | undefined; + } +} + +/** + * Creates a new mime tree node. Assumes 'multipart/*' as the content type + * if it is a branch, anything else counts as leaf. If rootNode is missing from + * the options, assumes this is the root. + */ +declare class MimeNode { + constructor(contentType?: string, options?: MimeNode.Options); + + /** Creates and appends a child node.Arguments provided are passed to MimeNode constructor */ + createChild(contentType: string, options?: MimeNode.Options): MimeNode; + + /** Appends an existing node to the mime tree. Removes the node from an existing tree if needed */ + appendChild(childNode: MimeNode): MimeNode; + + /** Replaces current node with another node */ + replace(node: MimeNode): MimeNode; + + /** Removes current node from the mime tree */ + remove(): this; + + /** + * Sets a header value. If the value for selected key exists, it is overwritten. + * You can set multiple values as well by using [{key:'', value:''}] or + * {key: 'value'} as the first argument. + */ + setHeader(key: string, value: string | string[]): this; + setHeader(headers: { [key: string]: string } | Array<{ key: string; value: string }>): this; + + /** + * Adds a header value. If the value for selected key exists, the value is appended + * as a new field and old one is not touched. + * You can set multiple values as well by using [{key:'', value:''}] or + * {key: 'value'} as the first argument. + */ + addHeader(key: string, value: string): this; + addHeader(headers: { [key: string]: string } | Array<{ key: string; value: string }>): this; + + /** Retrieves the first mathcing value of a selected key */ + getHeader(key: string): string; + + /** + * Sets body content for current node. If the value is a string, charset is added automatically + * to Content-Type (if it is text/*). If the value is a Buffer, you need to specify + * the charset yourself + */ + setContent(content: string | Buffer | Readable): this; + + /** Generate the message and return it with a callback or promise */ + build(callback: (err: Error | null, buf: Buffer) => void): void; + build(): Promise; + + getTransferEncoding(): string; + + /** Builds the header block for the mime node. Append \r\n\r\n before writing the content */ + buildHeaders(): string; + + /** + * Streams the rfc2822 message from the current node. If this is a root node, + * mandatory header fields are set if missing (Date, Message-Id, MIME-Version) + */ + createReadStream(options?: ReadableOptions): Readable; + + /** + * Appends a transform stream object to the transforms list. Final output + * is passed through this stream before exposing + */ + transform(transform: Transform): void; + + /** + * Appends a post process function. The functon is run after transforms and + * uses the following syntax + * + * processFunc(input) -> outputStream + */ + processFunc(processFunc: (outputStream: Readable) => Readable): void; + + stream(outputStream: Readable, options: ReadableOptions, done: (err?: Error | null) => void): void; + + /** Sets envelope to be used instead of the generated one */ + setEnvelope(envelope: Mail.Envelope): this; + + /** Generates and returns an object with parsed address fields */ + getAddresses(): MimeNode.Addresses; + + /** Generates and returns SMTP envelope with the sender address and a list of recipients addresses */ + getEnvelope(): MimeNode.Envelope; + + /** Returns Message-Id value. If it does not exist, then creates one */ + messageId(): string; + + /** Sets pregenerated content that will be used as the output of this node */ + setRaw(raw: string | Buffer | Readable): this; + + /** shared part of the unique multipart boundary */ + baseBoundary: string; + + /** a multipart boundary value */ + boundary?: string | false | undefined; + + /** Undocumented */ + boundaryPrefix: string; + + /* An array for possible child nodes */ + childNodes: MimeNode[]; + + /** body content for current node */ + content?: string | Buffer | Readable | undefined; + + /** Undocumented */ + contentType?: string | undefined; + + /** + * If date headers is missing and current node is the root, this value is used instead + */ + date: Date; + + /** Undocumented */ + disableFileAccess: boolean; + + /** Undocumented */ + disableUrlAccess: boolean; + + /** filename for an attachment node */ + filename?: string | undefined; + + /** Hostname for default message-id values */ + hostname?: string | undefined; + + /** If true, do not exclude Bcc from the generated headers */ + keepBcc: boolean; + + /** Undocumented */ + multipart?: boolean | undefined; + + /** + * If set to 'win' then uses \r\n, + * if 'linux' then \n. + * If not set (or `raw` is used) then newlines are kept as is. + */ + newline?: string | undefined; + + /** Undocumented */ + nodeCounter: number; + + /** method to normalize header keys for custom caseing */ + normalizeHeaderKey?: ((key: string) => string) | undefined; + + /* Immediate parent for this node (or undefined if not set) */ + parentNode?: MimeNode | undefined; + + /** root node for this tree */ + rootNode: MimeNode; + + /** either 'Q' (the default) or 'B' */ + textEncoding: "B" | "Q" | ""; +} + +export = MimeNode; diff --git a/types/nodemailer/v7/lib/mime-node/last-newline.d.ts b/types/nodemailer/v7/lib/mime-node/last-newline.d.ts new file mode 100644 index 00000000000000..fff661d7ca19f4 --- /dev/null +++ b/types/nodemailer/v7/lib/mime-node/last-newline.d.ts @@ -0,0 +1,9 @@ +/// + +import { Transform } from "stream"; + +declare class LastNewline extends Transform { + lastByte: boolean; +} + +export = LastNewline; diff --git a/types/nodemailer/v7/lib/qp/index.d.ts b/types/nodemailer/v7/lib/qp/index.d.ts new file mode 100644 index 00000000000000..232eb792e7c186 --- /dev/null +++ b/types/nodemailer/v7/lib/qp/index.d.ts @@ -0,0 +1,23 @@ +/// + +import { Transform, TransformOptions } from "stream"; + +/** Encodes a Buffer into a Quoted-Printable encoded string */ +export function encode(buffer: Buffer | string): string; + +/** Adds soft line breaks to a Quoted-Printable string */ +export function wrap(str: string, lineLength?: number): string; + +export interface EncoderOptions extends TransformOptions { + lineLength?: number | false | undefined; +} + +/** Creates a transform stream for encoding data to Quoted-Printable encoding */ +export class Encoder extends Transform { + options: EncoderOptions; + + inputBytes: number; + outputBytes: number; + + constructor(options?: EncoderOptions); +} diff --git a/types/nodemailer/v7/lib/sendmail-transport/index.d.ts b/types/nodemailer/v7/lib/sendmail-transport/index.d.ts new file mode 100644 index 00000000000000..fadd1398b4349a --- /dev/null +++ b/types/nodemailer/v7/lib/sendmail-transport/index.d.ts @@ -0,0 +1,53 @@ +/// + +import { Transport, TransportOptions } from "../.."; + +import * as shared from "../shared"; + +import Mail = require("../mailer"); +import MailMessage = require("../mailer/mail-message"); +import MimeNode = require("../mime-node"); + +declare namespace SendmailTransport { + type MailOptions = Mail.Options; + + interface Options extends MailOptions, TransportOptions { + sendmail: true; + /** path to the sendmail command (defaults to ‘sendmail’) */ + path?: string | undefined; + /** either ‘windows’ or ‘unix’ (default). Forces all newlines in the output to either use Windows syntax or Unix syntax */ + newline?: string | undefined; + /** an optional array of command line options to pass to the sendmail command (ie. ["-f", "foo@blurdybloop.com"]). This overrides all default arguments except for ’-i’ and recipient list so you need to make sure you have all required arguments set (ie. the ‘-f’ flag). */ + args?: string[] | undefined; + } + + interface SentMessageInfo { + envelope: MimeNode.Envelope; + messageId: string; + response: string; + accepted: Array; + rejected: Array; + pending: Array; + } +} + +declare class SendmailTransport implements Transport { + options: SendmailTransport.Options; + logger: shared.Logger; + mailer: Mail; + name: string; + version: string; + path: string; + args: string[] | false; + winbreak: boolean; + + constructor(options: SendmailTransport.Options); + + /** Compiles a mailcomposer message and forwards it to handler that sends it */ + send( + mail: MailMessage, + callback: (err: Error | null, info: SendmailTransport.SentMessageInfo) => void, + ): void; +} + +export = SendmailTransport; diff --git a/types/nodemailer/v7/lib/sendmail-transport/le-unix.d.ts b/types/nodemailer/v7/lib/sendmail-transport/le-unix.d.ts new file mode 100644 index 00000000000000..2f93965281422d --- /dev/null +++ b/types/nodemailer/v7/lib/sendmail-transport/le-unix.d.ts @@ -0,0 +1,7 @@ +/// + +import { Transform } from "stream"; + +declare class LeUnix extends Transform {} + +export = LeUnix; diff --git a/types/nodemailer/v7/lib/sendmail-transport/le-windows.d.ts b/types/nodemailer/v7/lib/sendmail-transport/le-windows.d.ts new file mode 100644 index 00000000000000..401ea2da6b8b10 --- /dev/null +++ b/types/nodemailer/v7/lib/sendmail-transport/le-windows.d.ts @@ -0,0 +1,7 @@ +/// + +import { Transform } from "stream"; + +declare class LeWindows extends Transform {} + +export = LeWindows; diff --git a/types/nodemailer/v7/lib/ses-transport/index.d.ts b/types/nodemailer/v7/lib/ses-transport/index.d.ts new file mode 100644 index 00000000000000..77f0c81be6d98d --- /dev/null +++ b/types/nodemailer/v7/lib/ses-transport/index.d.ts @@ -0,0 +1,114 @@ +/// + +import { EventEmitter } from "node:events"; + +import { Transport, TransportOptions } from "../.."; + +import * as shared from "../shared"; + +import Mail = require("../mailer"); +import MailMessage = require("../mailer/mail-message"); +import MimeNode = require("../mime-node"); + +declare namespace SESTransport { + /** + * Minimal structural shape of SESv2 SendEmail input. + * This is intentionally structural so @types/nodemailer does not require + * installing @aws-sdk/client-sesv2. + * + * If you want the full, exact type, install @aws-sdk/client-sesv2 in your + * app and use its types directly in your own code. + */ + interface SendEmailRequestLike { + FromEmailAddress?: string; + Destination?: { + ToAddresses?: string[]; + CcAddresses?: string[]; + BccAddresses?: string[]; + }; + ReplyToAddresses?: string[]; + Content?: unknown; + EmailTags?: Array<{ Name?: string; Value?: string }>; + ConfigurationSetName?: string; + ListManagementOptions?: unknown; + FeedbackForwardingEmailAddress?: string; + // Allow extra fields without forcing the SDK type package + [key: string]: unknown; + } + + /** Structural type matching SESv2Client from @aws-sdk/client-sesv2 */ + interface SESv2ClientLike { + send(command: unknown, options?: unknown): Promise<{ MessageId?: string }>; + config?: { + region?: string | (() => Promise); + }; + } + + /** + * Constructor type for SendEmailCommand from @aws-sdk/client-sesv2. + * The real type is: new(input: SendEmailCommandInput) => SendEmailCommand + * Contravariance prevents typing this more strictly without pulling in aws-sdk as a dependency. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + type SendEmailCommandConstructorLike = new(input: any) => unknown; + + interface MailOptions extends Mail.Options { + /** Options passed to AWS SESv2 SendEmailCommand */ + ses?: MailSesOptions | undefined; + } + + // Keep it as an interface for backward-compatibility + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface MailSesOptions extends Partial {} + + interface Options extends MailOptions, TransportOptions { + /** An object containing an instantiated SESv2Client and the SendEmailCommand class */ + SES: { + sesClient: SESv2ClientLike; + SendEmailCommand: SendEmailCommandConstructorLike; + }; + } + + interface SentMessageInfo { + /** an envelope object {from:'address', to:['address']} */ + envelope: MimeNode.Envelope; + /** the Message-ID header value. This value is derived from the response of SES API, so it differs from the Message-ID values used in logging. */ + messageId: string; + response: string; + accepted: Array; + rejected: Array; + pending: Array; + raw: Buffer; + } +} + +declare class SESTransport extends EventEmitter implements Transport { + options: SESTransport.Options; + + logger: shared.Logger; + mailer: Mail; + + name: string; + version: string; + + ses: SESTransport.Options["SES"]; + + constructor(options: SESTransport.Options); + + /** Undocumented */ + getRegion( + callback: (err: Error | null, region: string | undefined) => void, + ): void; + + /** Schedules a sending of a message */ + send( + mail: MailMessage, + callback: (err: Error | null, info: SESTransport.SentMessageInfo) => void, + ): void; + + /** Verifies SES configuration */ + verify(callback: (err: Error | null, success: true) => void): void; + verify(): Promise; +} + +export = SESTransport; diff --git a/types/nodemailer/v7/lib/shared/index.d.ts b/types/nodemailer/v7/lib/shared/index.d.ts new file mode 100644 index 00000000000000..20ca440e067a2e --- /dev/null +++ b/types/nodemailer/v7/lib/shared/index.d.ts @@ -0,0 +1,58 @@ +/// + +import SMTPConnection = require("../smtp-connection"); + +import stream = require("stream"); + +export type LoggerLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal"; + +export interface Logger { + level(level: LoggerLevel): void; + trace(...params: any[]): void; + debug(...params: any[]): void; + info(...params: any[]): void; + warn(...params: any[]): void; + error(...params: any[]): void; + fatal(...params: any[]): void; +} + +export interface ResolveHostnameOptions { + host?: string | undefined; + servername?: string | false | undefined; +} + +export interface ResolveHostnameValue { + host: string; + servername: string | false; + _cached?: true | undefined; +} + +export function resolveHostname( + options: ResolveHostnameOptions | null | undefined, + callback: (err: Error | null, value: ResolveHostnameValue) => void, +): void; + +/** Parses connection url to a structured configuration object */ +export function parseConnectionUrl(url: string): SMTPConnection.Options; +/** Returns a bunyan-compatible logger interface. Uses either provided logger or creates a default console logger */ +export function getLogger(options?: { [key: string]: any }, defaults?: { [key: string]: any }): Logger; +/** Wrapper for creating a callback than either resolves or rejects a promise based on input */ +export function callbackPromise(resolve: (...args: any[]) => void, reject: (err: Error) => void): () => void; +/** + * Resolves a String or a Buffer value for content value. Useful if the value + * is a Stream or a file or an URL. If the value is a Stream, overwrites + * the stream object with the resolved value (you can't stream a value twice). + * + * This is useful when you want to create a plugin that needs a content value, + * for example the `html` or `text` value as a String or a Buffer but not as + * a file path or an URL. + */ +export function resolveContent( + data: object | any[], + key: string | number, + callback: (err: Error | null, value: Buffer | string) => void, +): void; +export function resolveContent(data: object | any[], key: string | number): Promise; +/** Copies properties from source objects to target objects */ +export function assign(target: object, ...sources: object[]): object; +export function encodeXText(str: string): string; diff --git a/types/nodemailer/v7/lib/smtp-connection/data-stream.d.ts b/types/nodemailer/v7/lib/smtp-connection/data-stream.d.ts new file mode 100644 index 00000000000000..fa7a96b8f6bb5b --- /dev/null +++ b/types/nodemailer/v7/lib/smtp-connection/data-stream.d.ts @@ -0,0 +1,11 @@ +/// + +import { Transform } from "stream"; + +/** + * Escapes dots in the beginning of lines. Ends the stream with . + * Also makes sure that only sequences are used for linebreaks + */ +declare class DataStream extends Transform {} + +export = DataStream; diff --git a/types/nodemailer/v7/lib/smtp-connection/http-proxy-client.d.ts b/types/nodemailer/v7/lib/smtp-connection/http-proxy-client.d.ts new file mode 100644 index 00000000000000..078880ef9cb560 --- /dev/null +++ b/types/nodemailer/v7/lib/smtp-connection/http-proxy-client.d.ts @@ -0,0 +1,16 @@ +/// + +import { Socket } from "net"; +import { TLSSocket } from "tls"; + +/** + * Establishes proxied connection to destinationPort + */ +declare function httpProxyClient( + proxyUrl: string, + destinationPort: number, + destinationHost: string, + callback: (err: Error | null, socket: TLSSocket | Socket) => void, +): void; + +export = httpProxyClient; diff --git a/types/nodemailer/v7/lib/smtp-connection/index.d.ts b/types/nodemailer/v7/lib/smtp-connection/index.d.ts new file mode 100644 index 00000000000000..bf692fee476d42 --- /dev/null +++ b/types/nodemailer/v7/lib/smtp-connection/index.d.ts @@ -0,0 +1,270 @@ +/// + +import { EventEmitter } from "events"; +import * as net from "net"; +import { Readable } from "stream"; +import * as tls from "tls"; + +import * as shared from "../shared"; + +import MimeNode = require("../mime-node"); +import XOAuth2 = require("../xoauth2"); + +type ms = number; + +declare namespace SMTPConnection { + interface Credentials { + /** the username */ + user: string; + /** then password */ + pass: string; + } + + type OAuth2 = XOAuth2.Options; + + interface AuthenticationTypeCustom extends Credentials { + /** indicates the authetication type, defaults to ‘login’, other option is ‘oauth2’ or ‘custom’ */ + type: "custom" | "Custom" | "CUSTOM"; + method: string; + } + + interface AuthenticationTypeLogin extends Credentials { + /** indicates the authetication type, defaults to ‘login’, other option is ‘oauth2’ or ‘custom’ */ + type?: "login" | "Login" | "LOGIN" | undefined; + } + + interface AuthenticationTypeOAuth2 extends OAuth2 { + /** indicates the authetication type, defaults to ‘login’, other option is ‘oauth2’ or ‘custom’ */ + type?: "oauth2" | "OAuth2" | "OAUTH2" | undefined; + } + + type AuthenticationType = AuthenticationTypeCustom | AuthenticationTypeLogin | AuthenticationTypeOAuth2; + + interface AuthenticationCredentials { + /** normal authentication object */ + credentials: Credentials; + } + + interface AuthenticationOAuth2 { + /** if set then forces smtp-connection to use XOAuth2 for authentication */ + oauth2: OAuth2; + } + + interface CustomAuthenticationResponse { + command: string; + response: string; + status: number; + text: string; + code?: number | undefined; + } + + interface CustomAuthenticationContext { + auth: AuthenticationCredentials; + authMethod: string; + extensions: string[]; + authMethods: string[]; + maxAllowedSize: number | false; + sendCommand(cmd: string): Promise; + sendCommand(cmd: string, done: (err: Error | null, data: CustomAuthenticationResponse) => void): void; + resolve(): unknown; + reject(err: Error | string): unknown; + } + + interface CustomAuthenticationHandlers { + [method: string]: (ctx: CustomAuthenticationContext) => Promise | unknown; + } + + type DSNOption = "NEVER" | "SUCCESS" | "FAILURE" | "DELAY"; + + interface DSNOptions { + /** return either the full message ‘FULL’ or only headers ‘HDRS’ */ + ret?: "Full" | "HDRS" | undefined; + /** sender’s ‘envelope identifier’ for tracking */ + envid?: string | undefined; + /** when to send a DSN. Multiple options are OK - array or comma delimited. NEVER must appear by itself. */ + notify?: DSNOption | DSNOption[] | undefined; + /** original recipient */ + orcpt?: string | undefined; + } + + interface Envelope { + /** includes an address object or is set to false */ + from: string | false; + /** the recipient address or an array of addresses */ + to: string | string[]; + /** an optional value of the predicted size of the message in bytes. This value is used if the server supports the SIZE extension (RFC1870) */ + size?: number | undefined; + /** if true then inform the server that this message might contain bytes outside 7bit ascii range */ + use8BitMime?: boolean | undefined; + /** the dsn options */ + dsn?: DSNOptions | undefined; + } + + interface SMTPError extends NodeJS.ErrnoException { + /** string code identifying the error, for example ‘EAUTH’ is returned when authentication */ + code?: string | undefined; + /** the last response received from the server (if the error is caused by an error response from the server) */ + response?: string | undefined; + /** the numeric response code of the response string (if available) */ + responseCode?: number | undefined; + /** command which provoked an error */ + command?: string | undefined; + } + + interface SentMessageInfo { + /** an array of accepted recipient addresses. Normally this array should contain at least one address except when in LMTP mode. In this case the message itself might have succeeded but all recipients were rejected after sending the message. */ + accepted: string[]; + /** an array of rejected recipient addresses. This array includes both the addresses that were rejected before sending the message and addresses rejected after sending it if using LMTP */ + rejected: string[]; + /** if some recipients were rejected then this property holds an array of error objects for the rejected recipients */ + rejectedErrors?: SMTPError[] | undefined; + /** the last response received from the server */ + response: string; + /** how long was envelope prepared */ + envelopeTime: number; + /** how long was send stream prepared */ + messageTime: number; + /** how many bytes were streamed */ + messageSize: number; + } + + interface Options { + /** the hostname or IP address to connect to (defaults to ‘localhost’) */ + host?: string | undefined; + /** the port to connect to (defaults to 25 or 465) */ + port?: number | undefined; + /** defines authentication data */ + auth?: AuthenticationType | undefined; + /** defines if the connection should use SSL (if true) or not (if false) */ + secure?: boolean | undefined; + /** indicates that the provided socket has already been upgraded to TLS (if true) */ + secured?: boolean | undefined; + /** turns off STARTTLS support if true */ + ignoreTLS?: boolean | undefined; + /** forces the client to use STARTTLS. Returns an error if upgrading the connection is not possible or fails. */ + requireTLS?: boolean | undefined; + /** tries to use STARTTLS and continues normally if it fails */ + opportunisticTLS?: boolean | undefined; + /** optional hostname of the client, used for identifying to the server */ + name?: string | undefined; + /** the local interface to bind to for network connections */ + localAddress?: string | undefined; + /** how many milliseconds to wait for the connection to establish */ + connectionTimeout?: ms | undefined; + /** how many milliseconds to wait for the greeting after connection is established */ + greetingTimeout?: ms | undefined; + /** how many milliseconds of inactivity to allow */ + socketTimeout?: ms | undefined; + /** how many milliseconds to wait for the DNS requests to be resolved */ + dnsTimeout?: ms | undefined; + /** optional bunyan compatible logger instance. If set to true then logs to console. If value is not set or is false then nothing is logged */ + logger?: shared.Logger | boolean | undefined; + /** if set to true, then logs SMTP traffic without message content */ + transactionLog?: boolean | undefined; + /** if set to true, then logs SMTP traffic and message content, otherwise logs only transaction events */ + debug?: boolean | undefined; + /** defines preferred authentication method, e.g. ‘PLAIN’ */ + authMethod?: string | undefined; + /** defines additional options to be passed to the socket constructor, e.g. {rejectUnauthorized: true} */ + tls?: tls.ConnectionOptions | undefined; + /** initialized socket to use instead of creating a new one */ + socket?: net.Socket | undefined; + /** connected socket to use instead of creating and connecting a new one. If secure option is true, then socket is upgraded from plaintext to ciphertext */ + connection?: net.Socket | undefined; + customAuth?: CustomAuthenticationHandlers | undefined; + /** if true, uses LMTP instead of SMTP protocol */ + lmtp?: boolean | undefined; + } +} + +declare class SMTPConnection extends EventEmitter { + options: SMTPConnection.Options; + + logger: shared.Logger; + + id: string; + stage: "init" | "connected"; + + secureConnection: boolean; + alreadySecured: boolean; + + port: number; + host: string; + + name: string; + /** Expose version nr, just for the reference */ + version: string; + + /** If true, then the user is authenticated */ + authenticated: boolean; + /** If set to true, this instance is no longer active */ + destroyed: boolean; + /** Defines if the current connection is secure or not. If not, STARTTLS can be used if available */ + secure: boolean; + + lastServerResponse: string | false; + + /** The socket connecting to the server */ + _socket: net.Socket; + + constructor(options?: SMTPConnection.Options); + + /** Creates a connection to a SMTP server and sets up connection listener */ + connect(callback: (err?: SMTPConnection.SMTPError) => void): void; + /** Sends QUIT */ + quit(): void; + /** Closes the connection to the server */ + close(): void; + /** Authenticate user */ + login( + auth: + | SMTPConnection.AuthenticationCredentials + | SMTPConnection.AuthenticationOAuth2 + | SMTPConnection.Credentials, + callback: (err?: SMTPConnection.SMTPError) => void, + ): void; + /** Sends a message */ + send( + envelope: SMTPConnection.Envelope, + message: string | Buffer | Readable, + callback: (err: SMTPConnection.SMTPError | null, info: SMTPConnection.SentMessageInfo) => void, + ): void; + /** Resets connection state */ + reset(callback: (err?: SMTPConnection.SMTPError) => void): void; + + addListener(event: "connect" | "end", listener: () => void): this; + addListener(event: "error", listener: (err: SMTPConnection.SMTPError) => void): this; + + emit(event: "connect" | "end"): boolean; + emit(event: "error", error: Error): boolean; + + listenerCount(event: "connect" | "end" | "error"): number; + + listeners(event: "connect" | "end"): Array<() => void>; + listeners(event: "error"): Array<(err: SMTPConnection.SMTPError) => void>; + + off(event: "connect" | "end", listener: () => void): this; + off(event: "error", listener: (err: SMTPConnection.SMTPError) => void): this; + + on(event: "connect" | "end", listener: () => void): this; + on(event: "error", listener: (err: SMTPConnection.SMTPError) => void): this; + + once(event: "connect" | "end", listener: () => void): this; + once(event: "error", listener: (err: SMTPConnection.SMTPError) => void): this; + + prependListener(event: "connect" | "end", listener: () => void): this; + prependListener(event: "error", listener: (err: SMTPConnection.SMTPError) => void): this; + + prependOnceListener(event: "connect" | "end", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: SMTPConnection.SMTPError) => void): this; + + rawListeners(event: "connect" | "end"): Array<() => void>; + rawListeners(event: "error"): Array<(err: SMTPConnection.SMTPError) => void>; + + removeAllListener(event: "connect" | "end" | "error"): this; + + removeListener(event: "connect" | "end", listener: () => void): this; + removeListener(event: "error", listener: (err: SMTPConnection.SMTPError) => void): this; +} + +export = SMTPConnection; diff --git a/types/nodemailer/v7/lib/smtp-pool/index.d.ts b/types/nodemailer/v7/lib/smtp-pool/index.d.ts new file mode 100644 index 00000000000000..08364dea6a4df3 --- /dev/null +++ b/types/nodemailer/v7/lib/smtp-pool/index.d.ts @@ -0,0 +1,93 @@ +/// + +import { EventEmitter } from "events"; + +import { Transport, TransportOptions } from "../.."; +import * as shared from "../shared"; + +import Mail = require("../mailer"); +import MailMessage = require("../mailer/mail-message"); +import MimeNode = require("../mime-node"); +import SMTPConnection = require("../smtp-connection"); + +declare namespace SMTPPool { + interface MailOptions extends Mail.Options { + auth?: SMTPConnection.AuthenticationType | undefined; + dsn?: SMTPConnection.DSNOptions | undefined; + } + + interface Options extends MailOptions, TransportOptions, SMTPConnection.Options { + /** set to true to use pooled connections (defaults to false) instead of creating a new connection for every email */ + pool: true; + service?: string | undefined; + getSocket?(options: Options, callback: (err: Error | null, socketOptions: any) => void): void; // TODO http.ClientRequest? + url?: string | undefined; + /** the count of maximum simultaneous connections to make against the SMTP server (defaults to 5) */ + maxConnections?: number | undefined; + /** limits the message count to be sent using a single connection (defaults to 100). After maxMessages is reached the connection is dropped and a new one is created for the following messages */ + maxMessages?: number | undefined; + /** defines the time measuring period in milliseconds (defaults to 1000, ie. to 1 second) for rate limiting */ + rateDelta?: number | undefined; + /** limits the message count to be sent in rateDelta time. Once rateLimit is reached, sending is paused until the end of the measuring period. This limit is shared between connections, so if one connection uses up the limit, then other connections are paused as well. If rateLimit is not set then sending rate is not limited */ + rateLimit?: number | undefined; + } + + interface SentMessageInfo extends SMTPConnection.SentMessageInfo { + /** includes the envelope object for the message */ + envelope: MimeNode.Envelope; + /** most transports should return the final Message-Id value used with this property */ + messageId: string; + } +} + +/** + * Creates a SMTP pool transport object for Nodemailer + */ +declare class SMTPPool extends EventEmitter implements Transport { + options: SMTPPool.Options; + + mailer: Mail; + logger: shared.Logger; + + name: string; + version: string; + + idling: boolean; + + constructor(options?: SMTPPool.Options | string); + + /** Placeholder function for creating proxy sockets. This method immediatelly returns without a socket */ + getSocket(options: SMTPPool.Options, callback: (err: Error | null, socketOptions: any) => void): void; + + /** Sends an e-mail using the selected settings */ + send( + mail: MailMessage, + callback: (err: Error | null, info: SMTPPool.SentMessageInfo) => void, + ): void; + + /** Closes all connections in the pool. If there is a message being sent, the connection is closed later */ + close(): void; + + /** Returns true if there are free slots in the queue */ + isIdle(): boolean; + + /** Verifies SMTP configuration */ + verify(callback: (err: Error | null, success: true) => void): void; + verify(): Promise; + + addListener(event: "idle", listener: () => void): this; + + emit(event: "idle"): boolean; + + on(event: "idle", listener: () => void): this; + + once(event: "idle", listener: () => void): this; + + prependListener(event: "idle", listener: () => void): this; + + prependOnceListener(event: "idle", listener: () => void): this; + + listeners(event: "idle"): Array<() => void>; +} + +export = SMTPPool; diff --git a/types/nodemailer/v7/lib/smtp-pool/pool-resource.d.ts b/types/nodemailer/v7/lib/smtp-pool/pool-resource.d.ts new file mode 100644 index 00000000000000..e2947825be7ee3 --- /dev/null +++ b/types/nodemailer/v7/lib/smtp-pool/pool-resource.d.ts @@ -0,0 +1,66 @@ +/// + +import { EventEmitter } from "events"; + +import MailMessage = require("../mailer/mail-message"); +import * as shared from "../shared"; + +import SMTPPool = require("."); + +/** + * Creates an element for the pool + */ +declare class PoolResource extends EventEmitter { + pool: SMTPPool; + options: SMTPPool.Options; + logger: shared.Logger; + + messages: number; + available: boolean; + + constructor(pool: SMTPPool); + + /** Initiates a connection to the SMTP server */ + connect(callback: (err: Error | null, established: boolean) => void): void; + /** Sends an e-mail to be sent using the selected settings */ + send(mail: MailMessage, callback: (err: Error | null, info: SMTPPool.SentMessageInfo) => void): void; + /** Closes the connection */ + close(): void; + + addListener(event: "available", listener: () => void): this; + addListener(event: "close", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + + emit(event: "available"): boolean; + emit(event: "close"): boolean; + emit(event: "error", err: Error): boolean; + emit(event: string | symbol, ...args: any[]): boolean; + + on(event: "available", listener: () => void): this; + on(event: "close", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + + once(event: "available", listener: () => void): this; + once(event: "close", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + + prependListener(event: "available", listener: () => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: string | symbol, listener: (...args: any[]) => void): this; + + prependOnceListener(event: "available", listener: () => void): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this; + + removeListener(event: "available", listener: () => void): this; + removeListener(event: "close", listener: () => void): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: string | symbol, listener: (...args: any[]) => void): this; +} + +export = PoolResource; diff --git a/types/nodemailer/v7/lib/smtp-transport/index.d.ts b/types/nodemailer/v7/lib/smtp-transport/index.d.ts new file mode 100644 index 00000000000000..f9966aedd84e81 --- /dev/null +++ b/types/nodemailer/v7/lib/smtp-transport/index.d.ts @@ -0,0 +1,115 @@ +/// + +import { EventEmitter } from "events"; +import stream = require("stream"); + +import { Transport, TransportOptions } from "../.."; +import * as shared from "../shared"; + +import Mail = require("../mailer"); +import MailMessage = require("../mailer/mail-message"); +import MimeNode = require("../mime-node"); +import SMTPConnection = require("../smtp-connection"); +import XOAuth2 = require("../xoauth2"); + +declare namespace SMTPTransport { + interface AuthenticationTypeCustom extends SMTPConnection.Credentials { + type: "CUSTOM"; + method: string; + } + + interface AuthenticationTypeLogin { + type: "LOGIN"; + user: string; + credentials: SMTPConnection.Credentials; + method: string | false; + } + + interface AuthenticationTypeOAuth2 { + type: "OAUTH2"; + user: string; + oauth2: XOAuth2; + method: "XOAUTH2"; + } + + type AuthenticationType = AuthenticationTypeLogin | AuthenticationTypeOAuth2; + + interface MailOptions extends Mail.Options { + auth?: SMTPConnection.AuthenticationType | undefined; + dsn?: SMTPConnection.DSNOptions | undefined; + } + + interface Options extends MailOptions, TransportOptions, SMTPConnection.Options { + service?: string | undefined; + getSocket?(options: Options, callback: (err: Error | null, socketOptions: any) => void): void; // TODO http.ClientRequest? + url?: string | undefined; + } + + interface SentMessageInfo { + /** includes the envelope object for the message */ + envelope: MimeNode.Envelope; + /** most transports should return the final Message-Id value used with this property */ + messageId: string; + accepted: Array; + rejected: Array; + pending: Array; + response: string; + } +} + +declare class SMTPTransport extends EventEmitter implements Transport { + options: SMTPTransport.Options; + + mailer: Mail; + logger: shared.Logger; + + name: string; + version: string; + + auth: SMTPTransport.AuthenticationType; + + constructor(options: SMTPTransport.Options | string); + + /** Placeholder function for creating proxy sockets. This method immediatelly returns without a socket */ + getSocket(options: SMTPTransport.Options, callback: (err: Error | null, socketOptions: object) => void): void; + + getAuth( + authOpts: SMTPConnection.AuthenticationTypeLogin | SMTPConnection.AuthenticationTypeOAuth2, + ): SMTPTransport.AuthenticationType; + + /** Sends an e-mail using the selected settings */ + send( + mail: MailMessage, + callback: (err: Error | null, info: SMTPTransport.SentMessageInfo) => void, + ): void; + + /** Verifies SMTP configuration */ + verify(callback: (err: Error | null, success: true) => void): void; + verify(): Promise; + + /** Releases resources */ + close(): void; + + addListener(event: "close", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + + emit(event: "close"): boolean; + emit(event: "error", error: Error): boolean; + + on(event: "close", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + + once(event: "close", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + + prependListener(event: "close", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + + listeners(event: "close"): Array<() => void>; + listeners(event: "error"): Array<(err: Error) => void>; +} + +export = SMTPTransport; diff --git a/types/nodemailer/v7/lib/stream-transport/index.d.ts b/types/nodemailer/v7/lib/stream-transport/index.d.ts new file mode 100644 index 00000000000000..bea46ef5a3224d --- /dev/null +++ b/types/nodemailer/v7/lib/stream-transport/index.d.ts @@ -0,0 +1,59 @@ +/// + +import { EventEmitter } from "events"; +import { Readable } from "stream"; + +import { Transport, TransportOptions } from "../.."; + +import * as shared from "../shared"; + +import Mail = require("../mailer"); +import MailMessage = require("../mailer/mail-message"); +import MimeNode = require("../mime-node"); + +declare namespace StreamTransport { + type MailOptions = Mail.Options; + + interface Options extends MailOptions, TransportOptions { + streamTransport: true; + /** if true, then returns the message as a Buffer object instead of a stream */ + buffer?: boolean | undefined; + /** either ‘windows’ or ‘unix’ (default). Forces all newlines in the output to either use Windows syntax or Unix syntax */ + newline?: string | undefined; + } + + interface SentMessageInfo { + /** an envelope object {from:‘address’, to:[‘address’]} */ + envelope: MimeNode.Envelope; + /** the Message-ID header value */ + messageId: string; + /** either stream (default) of buffer depending on the options */ + message: Buffer | Readable; + accepted: Array; + rejected: Array; + pending: Array; + response: string; + } +} + +declare class StreamTransport implements Transport { + options: StreamTransport.Options; + + logger: shared.Logger; + mailer: Mail; + + name: string; + version: string; + + winbreak: boolean; + + constructor(options: StreamTransport.Options); + + /** Compiles a mailcomposer message and forwards it to handler that sends it */ + send( + mail: MailMessage, + callback: (err: Error | null, info: StreamTransport.SentMessageInfo) => void, + ): void; +} + +export = StreamTransport; diff --git a/types/nodemailer/v7/lib/well-known/index.d.ts b/types/nodemailer/v7/lib/well-known/index.d.ts new file mode 100644 index 00000000000000..624ec1608a9ef4 --- /dev/null +++ b/types/nodemailer/v7/lib/well-known/index.d.ts @@ -0,0 +1,6 @@ +import SMTPConnection = require("../smtp-connection"); + +/** Resolves SMTP config for given key. Key can be a name (like 'Gmail'), alias (like 'Google Mail') or an email address (like 'test@googlemail.com'). */ +declare function wellKnown(key: string): SMTPConnection.Options | false; + +export = wellKnown; diff --git a/types/nodemailer/v7/lib/xoauth2/index.d.ts b/types/nodemailer/v7/lib/xoauth2/index.d.ts new file mode 100644 index 00000000000000..cea2e9c07b33d3 --- /dev/null +++ b/types/nodemailer/v7/lib/xoauth2/index.d.ts @@ -0,0 +1,114 @@ +/// + +import * as http from "http"; +import { Readable, Stream } from "stream"; + +import * as shared from "../shared"; + +type ms = number; +type s = number; + +declare namespace XOAuth2 { + interface Options { + /** User e-mail address */ + user?: string | undefined; + /** Client ID value */ + clientId?: string | undefined; + /** Client secret value */ + clientSecret?: string | undefined; + /** Refresh token for an user */ + refreshToken?: string | undefined; + /** Endpoint for token generation, defaults to 'https://accounts.google.com/o/oauth2/token' */ + accessUrl?: string | undefined; + /** An existing valid accessToken */ + accessToken?: string | undefined; + /** Private key for JSW */ + privateKey?: string | { key: string; passphrase: string } | undefined; + /** Optional Access Token expire time in ms */ + expires?: ms | undefined; + /** Optional TTL for Access Token in seconds */ + timeout?: s | undefined; + /** Function to run when a new access token is required */ + provisionCallback?( + user: string, + renew: boolean, + callback: (err: Error | null, accessToken: string, expires: number) => void, + ): void; + serviceClient?: string | undefined; + } + + interface Token { + user: string; + accessToken: string; + expires: number; + } + + interface RequestParams { + customHeaders?: http.OutgoingHttpHeaders | undefined; + } +} + +declare class XOAuth2 extends Stream { + options: XOAuth2.Options; + logger: shared.Logger; + accessToken: string | false; + expires: number; + + constructor(options?: XOAuth2.Options, logger?: shared.Logger); + + /** Returns or generates (if previous has expired) a XOAuth2 token */ + getToken(renew: boolean, callback: (err: Error | null, accessToken: string) => void): void; + + /** Updates token values */ + updateToken(accessToken: string, timeout: s): XOAuth2.Token; + + /** Generates a new XOAuth2 token with the credentials provided at initialization */ + generateToken(callback: (err: Error | null, accessToken: string) => void): void; + + /** Converts an access_token and user id into a base64 encoded XOAuth2 token */ + buildXOAuth2Token(accessToken: string): string; + + /** + * Custom POST request handler. + * This is only needed to keep paths short in Windows – usually this module + * is a dependency of a dependency and if it tries to require something + * like the request module the paths get way too long to handle for Windows. + * As we do only a simple POST request we do not actually require complicated + * logic support (no redirects, no nothing) anyway. + */ + postRequest( + url: string, + payload: string | Buffer | Readable | { [key: string]: string }, + params: XOAuth2.RequestParams, + callback: (err: Error | null, buf: Buffer) => void, + ): void; + + /** Encodes a buffer or a string into Base64url format */ + toBase64URL(data: Buffer | string): string; + + /** Creates a JSON Web Token signed with RS256 (SHA256 + RSA) */ + jwtSignRS256(payload: object): string; + + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "token", listener: (token: XOAuth2.Token) => void): this; + + emit(event: "error", error: Error): boolean; + emit(event: "token", token: XOAuth2.Token): boolean; + + on(event: "error", listener: (err: Error) => void): this; + on(event: "token", listener: (token: XOAuth2.Token) => void): this; + + once(event: "error", listener: (err: Error) => void): this; + once(event: "token", listener: (token: XOAuth2.Token) => void): this; + + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "end", listener: (token: XOAuth2.Token) => void): this; + + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "end", listener: (token: XOAuth2.Token) => void): this; + + listeners(event: "error"): Array<(err: Error) => void>; + listeners(event: "end"): Array<(token: XOAuth2.Token) => void>; +} + +export = XOAuth2; diff --git a/types/nodemailer/v7/nodemailer-tests.ts b/types/nodemailer/v7/nodemailer-tests.ts new file mode 100644 index 00000000000000..967dde403b3acd --- /dev/null +++ b/types/nodemailer/v7/nodemailer-tests.ts @@ -0,0 +1,2085 @@ +import { SendEmailCommand, SESv2Client } from "@aws-sdk/client-sesv2"; + +import * as nodemailer from "nodemailer"; + +import addressparser = require("nodemailer/lib/addressparser"); +import base64 = require("nodemailer/lib/base64"); +import DKIM = require("nodemailer/lib/dkim"); +import fetch = require("nodemailer/lib/fetch"); +import Cookies = require("nodemailer/lib/fetch/cookies"); +import JSONTransport = require("nodemailer/lib/json-transport"); +import Mail = require("nodemailer/lib/mailer"); +import MailComposer = require("nodemailer/lib/mail-composer"); +import MailMessage = require("nodemailer/lib/mailer/mail-message"); +import mimeFuncs = require("nodemailer/lib/mime-funcs"); +import mimeTypes = require("nodemailer/lib/mime-funcs/mime-types"); +import MimeNode = require("nodemailer/lib/mime-node"); +import qp = require("nodemailer/lib/qp"); +import SendmailTransport = require("nodemailer/lib/sendmail-transport"); +import SESTransport = require("nodemailer/lib/ses-transport"); +import shared = require("nodemailer/lib/shared"); +import SMTPConnection = require("nodemailer/lib/smtp-connection"); +import SMTPPool = require("nodemailer/lib/smtp-pool"); +import SMTPTransport = require("nodemailer/lib/smtp-transport"); +import StreamTransport = require("nodemailer/lib/stream-transport"); +import wellKnown = require("nodemailer/lib/well-known"); +import XOAuth2 = require("nodemailer/lib/xoauth2"); +import LeWindows = require("nodemailer/lib/sendmail-transport/le-windows"); +import LeUnix = require("nodemailer/lib/sendmail-transport/le-unix"); + +import * as fs from "fs"; +import stream from "stream"; + +// mock aws-sdk +const aws = { + SES: class MockSES { + constructor(options?: object) {} + }, + config: { + loadFromPath: (path: string): void => {}, + }, +}; + +// 1. Nodemailer + +function nodemailer_test() { + // Generate test SMTP service account from ethereal.email + // Only needed if you don't have a real mail account for testing + nodemailer.createTestAccount((err, account) => { + if (err) { + console.log(err); + return; + } + // create reusable transporter object using the default SMTP transport + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.ethereal.email", + port: 587, + secure: false, // true for 465, false for other ports + auth: { + user: account.user, // generated ethereal user + pass: account.pass, // generated ethereal password + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + // setup email data with unicode symbols + const mailOptions: Mail.Options = { + from: "\"Fred Foo 👻\" ", // sender address + to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers + subject: "Hello ✔", // Subject line + text: "Hello world?", // plain text body + html: "Hello world?", // html body + attachDataUrls: false, + }; + + // send mail with defined transport object + transporter.sendMail(mailOptions, (err, info) => { + err satisfies Error | null; + info satisfies SMTPTransport.SentMessageInfo; + // @ts-expect-error - info is `SMTPTransport.SentMessageInfo`. + info satisfies SMTPPool.SentMessageInfo; + }); + }); +} + +// createTransport type + +function create_transport(): nodemailer.Transporter { + const smtpConfig: SMTPTransport.Options = { + host: "smtp.example.com", + port: 587, + secure: false, // upgrade later with STARTTLS + auth: { + user: "username", + pass: "password", + }, + }; + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(smtpConfig); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + return transporter; +} + +// 3. Message configuration + +// Commmon fields + +function message_common_fields_test() { + const message: Mail.Options = { + from: "sender@server.com", + to: "receiver@sender.com", + subject: "Message title", + text: "Plaintext version of the message", + html: "

HTML version of the message

", + }; +} + +// Array variant of common fields + +function message_common_fields_array_test() { + const message: Mail.Options = { + from: ["sender@server.com", { address: "sender2@server.com", name: "Sender2" }], + to: ["receiver@sender.com", { address: "receiver2@sender.com", name: "Receiver2" }], + cc: ["ccdreceiver@sender.com"], + bcc: ["bccdreceiver@sender.com"], + }; +} + +// More advanced fields + +function message_more_advanced_fields_test() { + const message: Mail.Options = { + headers: { + "My-Custom-Header": "header value", + }, + date: new Date("2000-01-01 00:00:00"), + }; + + const htmlstream = fs.createReadStream("content.html"); + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + transporter.sendMail({ html: htmlstream }, (err, info) => { + err satisfies Error | null; + info satisfies SMTPTransport.SentMessageInfo; + // @ts-expect-error - info is `SMTPTransport.SentMessageInfo`. + info satisfies SMTPPool.SentMessageInfo; + }); +} + +// 3. Attachments + +function message_attachments_test() { + const message: Mail.Options = { + attachments: [ + { + // utf-8 string as an attachment + filename: "text1.txt", + content: "hello world!", + }, + { + // binary buffer as an attachment + filename: "text2.txt", + content: new Buffer("hello world!", "utf-8"), + }, + { + // file on disk as an attachment + filename: "text3.txt", + path: "/path/to/file.txt", // stream this file + }, + { + // filename and content type is derived from path + path: "/path/to/file.txt", + }, + { + // stream as an attachment + filename: "text4.txt", + content: fs.createReadStream("file.txt"), + contentTransferEncoding: "quoted-printable", + }, + { + // define custom content type for the attachment + filename: "text.bin", + content: "hello world!", + contentType: "text/plain", + contentTransferEncoding: "7bit", + contentDisposition: "attachment", + }, + { + // use URL as an attachment + filename: "license.txt", + path: "https://raw.github.com/nodemailer/nodemailer/master/LICENSE", + }, + { + // encoded string as an attachment + filename: "text1.txt", + content: "aGVsbG8gd29ybGQh", + encoding: "base64", + contentTransferEncoding: "base64", + }, + { + // data uri as an attachment + path: "data:text/plain;base64,aGVsbG8gd29ybGQ=", + contentDisposition: "inline", + contentTransferEncoding: false, + }, + { + // use pregenerated MIME node + raw: "Content-Type: text/plain\r\n" // tslint:disable-line prefer-template + + "Content-Disposition: attachment;\r\n" + + "\r\n" + + "Hello world!", + }, + ], + }; +} + +// 3. Alternatives + +function message_alternatives_test() { + const message: Mail.Options = { + html: "Hello world!", + alternatives: [ + { + contentType: "text/x-web-markdown", + content: "**Hello world!**", + }, + ], + }; +} + +// 3. Address object + +function message_address_object_test() { + const message: Mail.Options = { + to: "foobar@blurdybloop.com, \"Ноде Майлер\" , \"Name, User\" ", + cc: ["foobar@blurdybloop.com", "\"Ноде Майлер\" ", "\"Name, User\" "], + bcc: [ + "foobar@blurdybloop.com", + { + name: "Майлер, Ноде", + address: "foobar@blurdybloop.com", + }, + ], + }; +} + +// 3. Calendar events + +// Send a REQUEST event as a string + +function message_calendar_request_test() { + const content = "BEGIN:VCALENDAR\r\nPRODID:-//ACME/DesktopCalendar//EN\r\nMETHOD:REQUEST\r\n..."; + + const message: Mail.Options = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Appointment", + text: "Please see the attached appointment", + icalEvent: { + filename: "invitation.ics", + method: "request", + content, + }, + }; +} + +// Send a PUBLISH event from a file + +function message_calendar_publish_test() { + const message: Mail.Options = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Appointment", + text: "Please see the attached appointment", + icalEvent: { + method: "PUBLISH", + path: "/path/to/file", + }, + }; +} + +// Send a CANCEL event from an URL + +function message_calendar_cancel_test() { + const message: Mail.Options = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Appointment", + text: "Please see the attached appointment", + icalEvent: { + method: "CANCEL", + href: "http://www.example.com/events?event=123", + }, + }; +} + +// 3. Embedded images + +function message_embedded_images_test() { + const message: Mail.Options = { + html: "Embedded image: ", + attachments: [ + { + filename: "image.png", + path: "/path/to/file", + cid: "unique@nodemailer.com", // same cid value as in the html img src + }, + ], + }; +} + +// 3. List headers + +// Setup different List-* headers + +function message_list_headers_test() { + const message: Mail.Options = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "List Message", + text: "I hope no-one unsubscribes from this list!", + list: { + // List-Help: + help: "admin@example.com?subject=help", + // List-Unsubscribe: (Comment) + unsubscribe: { + url: "http://example.com", + comment: "Comment", + }, + // List-Subscribe: + // List-Subscribe: (Subscribe) + subscribe: [ + "admin@example.com?subject=subscribe", + { + url: "http://example.com", + comment: "Subscribe", + }, + ], + // List-Post: , (Post) + post: [ + [ + "http://example.com/post", + { + url: "admin@example.com?subject=post", + comment: "Post", + }, + ], + ], + }, + }; +} + +// 3. Custom headers + +// Set custom headers + +function message_custom_headers_test() { + const message: Mail.Options = { + headers: { + "x-my-key": "header value", + "x-another-key": "another value", + }, + }; +} + +// Multiple rows with the same key + +function message_multiple_rows_with_the_same_key_test() { + const message: Mail.Options = { + headers: { + "x-my-key": ["value for row 1", "value for row 2", "value for row 3"], + }, + }; +} + +// Prepared headers + +function message_prepared_headers_test() { + const message: Mail.Options = { + headers: { + "x-processed": "a really long header or value with non-ascii characters 👮", + "x-unprocessed": { + prepared: true, + value: "a really long header or value with non-ascii characters 👮", + }, + }, + }; +} + +// 3. Custom source + +// Use string as a message body + +function message_string_body_test() { + const message: Mail.Options = { + envelope: { + from: "sender@example.com", + to: ["recipient@example.com"], + }, + raw: `From: sender@example.com +To: recipient@example.com +Subject: test message + +Hello world!`, + }; +} + +// Set EML file as message body + +function message_eml_file_test() { + const message: Mail.Options = { + envelope: { + from: "sender@example.com", + to: ["recipient@example.com"], + }, + raw: { + path: "/path/to/message.eml", + }, + }; +} + +// Set string as attachment body + +function message_string_attachment_test() { + const message: Mail.Options = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Custom attachment", + attachments: [ + { + raw: `Content-Type: text/plain +Content-Disposition: attachment + +Attached text file`, + }, + ], + }; +} + +// xMailer + +function message_xmailer_false_test() { + const message: Mail.Options = { + xMailer: false, + }; +} + +function message_xmailer_string_test() { + const message: Mail.Options = { + xMailer: "foobar", + }; +} + +// 4. SMTP transport + +// Single connection + +function smtp_single_connection_test() { + const smtpConfig: SMTPTransport.Options = { + host: "smtp.example.com", + port: 587, + secure: false, // upgrade later with STARTTLS + auth: { + user: "username", + pass: "password", + }, + }; + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(smtpConfig); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// Pooled connection + +function smtp_pooled_connection_test() { + const smtpConfig: SMTPPool.Options = { + pool: true, + host: "smtp.example.com", + port: 465, + secure: true, // use TLS + auth: { + user: "username", + pass: "password", + }, + }; + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(smtpConfig); + + let transporterDefault: SMTPPool.Options; + transporterDefault = transporter._defaults; +} + +// Allow self-signed certificates + +function smtp_self_signed_test() { + const smtpConfig: SMTPTransport.Options = { + host: "my.smtp.host", + port: 465, + secure: true, // use TLS + auth: { + user: "username", + pass: "pass", + }, + tls: { + // do not fail on invalid certs + rejectUnauthorized: false, + }, + }; + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(smtpConfig); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// Verify SMTP connection configuration + +function smtp_verify_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + transporter.verify((error, success) => { + if (error) { + console.log(error); + } else { + console.log("Server is ready to take our messages"); + } + }); +} + +// 4. SMTP envelope + +function smtp_envelope_test() { + const message: Mail.Options = { + from: "mailer@nodemailer.com", // listed in rfc822 message header + to: "daemon@nodemailer.com", // listed in rfc822 message header + envelope: { + from: "Daemon ", // used as MAIL FROM: address for SMTP + to: "mailer@nodemailer.com, Mailer ", // used as RCPT TO: address for SMTP + }, + }; +} + +// 4. Pooled SMTP + +// transporter.close() + +function smtp_pool_close_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ pool: true }); + + let transporterDefault: SMTPPool.Options; + transporterDefault = transporter._defaults; + + transporter.close(); +} + +// Event:‘idle’ + +function smtp_pool_idle_test() { + const messages = [{ raw: "list of messages" }]; + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ pool: true }); + + let transporterDefault: SMTPPool.Options; + transporterDefault = transporter._defaults; + + transporter.on("idle", () => { + // send next message from the pending queue + while (transporter.isIdle() && messages.length) { + transporter.sendMail(messages.shift()!); + } + }); +} + +// 4. Testing SMTP + +// Create a testing account on the fly + +function smtp_test_account_test() { + nodemailer.createTestAccount((err, account) => { + if (!err) { + // create reusable transporter object using the default SMTP transport + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.ethereal.email", + port: 587, + secure: false, // true for 465, false for other ports + auth: { + user: account.user, // generated ethereal user + pass: account.pass, // generated ethereal password + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + } + }); +} + +// Use environment specific SMTP settings + +function smtp_info_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + transporter.sendMail({}).then((info) => { + info satisfies SMTPTransport.SentMessageInfo; + // @ts-expect-error - info is `SMTPTransport.SentMessageInfo`. + info satisfies SMTPPool.SentMessageInfo; + }); +} + +// 4. OAuth2 + +// Using custom token handling + +function oauth2_token_handling_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + const userTokens: { [key: string]: string } = {}; + transporter.set("oauth2_provision_cb", (user, renew, callback) => { + const accessToken = userTokens[user]; + if (!accessToken) { + callback(new Error("Unknown user")); + } else { + callback(null, accessToken); + } + }); +} + +// Token update notifications + +function oauth2_token_update_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + transporter.on("token", token => { + console.log("A new access token was generated"); + console.log("User: %s", token.user); + console.log("Access Token: %s", token.accessToken); + console.log("Expires: %s", new Date(token.expires)); + }); +} + +// Authenticate using existing token + +function oauth2_existing_token_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: "OAuth2", + user: "user@example.com", + accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x", + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// Custom handler + +function oauth2_custom_handler_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: "OAuth2", + user: "user@example.com", + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + const userTokens: { [key: string]: string } = {}; + + transporter.set("oauth2_provision_cb", (user, renew, callback) => { + const accessToken = userTokens[user]; + if (!accessToken) { + callback(new Error("Unknown user")); + } else { + callback(null, accessToken); + } + }); +} + +// Set up 3LO authentication + +function oauth2_3lo_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: "OAuth2", + user: "user@example.com", + clientId: "000000000000-xxx0.apps.googleusercontent.com", + clientSecret: "XxxxxXXxX0xxxxxxxx0XXxX0", + refreshToken: "1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx", + accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x", + expires: 1484314697598, + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// Set up 2LO authentication + +function oauth2_2lo_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: "OAuth2", + user: "user@example.com", + clientId: "113600000000000000000", + clientSecret: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x", + expires: 1484314697598, + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// Provide authentication details with message options + +function oauth2_message_options_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: "OAuth2", + clientId: "000000000000-xxx.apps.googleusercontent.com", + clientSecret: "XxxxxXXxX0xxxxxxxx0XXxX0", + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + const auth: SMTPConnection.AuthenticationTypeOAuth2 = { + user: "user@example.com", + refreshToken: "1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx", + accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x", + expires: 1484314697598, + }; + + const options: SMTPTransport.MailOptions = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Message", + text: "I hope this message gets through!", + auth: { + user: "user@example.com", + refreshToken: "1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx", + accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x", + expires: 1484314697598, + }, + }; + + transporter.sendMail(options); +} + +function oauth2_privision_cb_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: "OAuth2", + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + const userTokens: { [key: string]: string } = {}; + + transporter.set("oauth2_provision_cb", (user, renew, callback) => { + const accessToken = userTokens[user]; + if (!accessToken) { + callback(new Error("Unknown user")); + } else { + callback(null, accessToken); + } + }); + + const options: SMTPTransport.MailOptions = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Message", + text: "I hope this message gets through!", + auth: { + user: "user@example.com", + }, + }; + + transporter.sendMail(options); +} + +// Set up XOAuth2 authentication + +function oauth2_xoauth2_test() { + const xoauth2 = new XOAuth2({ + user: "test@example.com", + clientId: "{Client ID}", + clientSecret: "{Client Secret}", + refreshToken: "saladus", + accessUrl: "http://localhost:8993/", + accessToken: "abc", + timeout: 3600, + }); + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + type: "OAUTH2", + user: "user@example.com", + oauth2: xoauth2, + method: "XOAUTH2", + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// Set up custom authentication + +async function custom_auth_async_test() { + const account = await nodemailer.createTestAccount(); + + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport( + { + host: account.smtp.host, + port: account.smtp.port, + secure: account.smtp.secure, + auth: { + type: "custom", + + user: account.user, + pass: account.pass, + + method: "x-login", + }, + logger: false, + debug: false, + + customAuth: { + // can create multiple handlers + "x-login": async ctx => { + // This custom method implements AUTH LOGIN even though Nodemailer supports it natively. + // AUTH LOGIN mechanism includes multiple steps, so it's great for a demo nevertheless + + console.log("Performing custom authentication for %s", ctx.auth.credentials.user); + console.log("Supported extensions: %s", ctx.extensions.join(", ")); + console.log("Supported auth methods: %s", ctx.authMethods.join(", ")); + + if (ctx.authMethods.indexOf("LOGIN") === -1) { + console.log("Server does not support AUTH LOGIN"); + throw new Error("Can not log in"); + } + console.log("AUTH LOGIN is supported, proceeding with login..."); + + let cmd; + + cmd = await ctx.sendCommand("AUTH LOGIN"); + if (cmd.status !== 334) { + // expecting '334 VXNlcm5hbWU6' + throw new Error("Invalid login sequence while waiting for \"334 VXNlcm5hbWU6\""); + } + + console.log("Sending username: %s", ctx.auth.credentials.user); + cmd = await ctx.sendCommand(Buffer.from(ctx.auth.credentials.user, "utf-8").toString("base64")); + if (cmd.status !== 334) { + // expecting '334 UGFzc3dvcmQ6' + throw new Error("Invalid login sequence while waiting for \"334 UGFzc3dvcmQ6\""); + } + + console.log("Sending password: %s", "*".repeat(ctx.auth.credentials.pass.length)); + cmd = await ctx.sendCommand(Buffer.from(ctx.auth.credentials.pass, "utf-8").toString("base64")); + if (cmd.status < 200 || cmd.status >= 300) { + // expecting a 235 response, just in case allow everything in 2xx range + throw new Error("User failed to authenticate"); + } + + console.log("User authenticated! (%s)", cmd.response); + + // all checks passed + return true; + }, + }, + }, + { + // default message fields + + // sender info + from: "Pangalink ", + headers: { + "X-Laziness-level": "1000", // just an example header, no need to use this + }, + }, + ); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +async function custom_auth_cb_test() { + const account = await nodemailer.createTestAccount(); + + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport( + { + host: account.smtp.host, + port: account.smtp.port, + secure: account.smtp.secure, + auth: { + type: "custom", + + user: account.user, + pass: account.pass, + + method: "x-login", + }, + logger: false, + debug: false, + + customAuth: { + // can create multiple handlers + "x-login": ctx => { + // This custom method implements AUTH LOGIN even though Nodemailer supports it natively. + // AUTH LOGIN mechanism includes multiple steps, so it's great for a demo nevertheless + + console.log("Performing custom authentication for %s", ctx.auth.credentials.user); + console.log("Supported extensions: %s", ctx.extensions.join(", ")); + console.log("Supported auth methods: %s", ctx.authMethods.join(", ")); + + if (ctx.authMethods.indexOf("LOGIN") === -1) { + console.log("Server does not support AUTH LOGIN"); + return ctx.reject(new Error("Can not log in")); + } + console.log("AUTH LOGIN is supported, proceeding with login..."); + + ctx.sendCommand("AUTH LOGIN", (err, cmd) => { + if (err) { + return ctx.reject(err); + } + + if (cmd.status !== 334) { + // expecting '334 VXNlcm5hbWU6' + return ctx.reject("Invalid login sequence while waiting for \"334 VXNlcm5hbWU6\""); + } + + console.log("Sending username: %s", ctx.auth.credentials.user); + ctx.sendCommand( + Buffer.from(ctx.auth.credentials.user, "utf-8").toString("base64"), + (err, cmd) => { + if (err) { + return ctx.reject(err); + } + if (cmd.status !== 334) { + // expecting '334 UGFzc3dvcmQ6' + return ctx.reject("Invalid login sequence while waiting for \"334 UGFzc3dvcmQ6\""); + } + + console.log("Sending password: %s", "*".repeat(ctx.auth.credentials.pass.length)); + ctx.sendCommand( + Buffer.from(ctx.auth.credentials.pass, "utf-8").toString("base64"), + (err, cmd) => { + if (err) { + return ctx.reject(err); + } + if (cmd.status < 200 || cmd.status >= 300) { + // expecting a 235 response, just in case allow everything in 2xx range + return ctx.reject("User failed to authenticate"); + } + + console.log("User authenticated! (%s)", cmd.response); + + // all checks passed + return ctx.resolve(); + }, + ); + }, + ); + }); + }, + }, + }, + { + // default message fields + + // sender info + from: "Pangalink ", + headers: { + "X-Laziness-level": "1000", // just an example header, no need to use this + }, + }, + ); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// 5. Sendmail transport + +// Send a message using specific binary + +function sendmail_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + sendmail: true, + newline: "unix", + path: "/usr/sbin/sendmail", + }); + + let transporterDefault: SendmailTransport.Options; + transporterDefault = transporter._defaults; + + transporter.sendMail( + { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Message", + text: "I hope this message gets delivered!", + }, + (err, info) => { + err satisfies Error | null; + info satisfies SendmailTransport.SentMessageInfo; + // @ts-expect-error - info is `SendmailTransport.SentMessageInfo`. + info satisfies SMTPPool.SentMessageInfo; + }, + ); +} + +// line ending transforms using windows-style newlines + +function sendmail_line_endings_windows_test() { + function process_le(mail: MailMessage) { + const input = mail.message.createReadStream(); + input.pipe(new LeWindows()); + } +} + +// line ending transforms using unix-style newlines + +function sendmail_line_endings_unix_test() { + function process_le(mail: MailMessage) { + const input = mail.message.createReadStream(); + input.pipe(new LeUnix()); + } +} + +// 5. SES transport + +// Send a message using SES transport + +function ses_test() { + // configure AWS SDK + aws.config.loadFromPath("config.json"); + + // create Nodemailer SES transporter + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + SES: { + sesClient: new SESv2Client(), + SendEmailCommand, + }, + component: "ses-transport", + }); + + let transporterDefault: SESTransport.Options; + transporterDefault = transporter._defaults; + + const options: SESTransport.MailOptions = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Message", + text: "I hope this message gets sent!", + ses: { + // optional extra arguments for SendRawEmail + EmailTags: [ + { + Name: "tag name", + Value: "tag value", + }, + ], + }, + }; + + // send some mail + transporter.sendMail(options, (err, info) => { + err satisfies Error | null; + info satisfies SESTransport.SentMessageInfo; + // @ts-expect-error - info is `SESTransport.SentMessageInfo`. + info satisfies SMTPPool.SentMessageInfo; + }); +} + +// 5. Stream transport + +// Stream a message with windows-style newlines + +function stream_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + streamTransport: true, + newline: "windows", + }); + + let transporterDefault: StreamTransport.Options; + transporterDefault = transporter._defaults; + + transporter.sendMail( + { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Message", + text: "I hope this message gets streamed!", + }, + (err, info) => { + err satisfies Error | null; + info satisfies StreamTransport.SentMessageInfo; + // @ts-expect-error - info is `StreamTransport.SentMessageInfo`. + info satisfies SMTPPool.SentMessageInfo; + }, + ); +} + +// Create a buffer with unix-style newlines + +function stream_buffer_unix_newlines_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + streamTransport: true, + newline: "unix", + buffer: true, + normalizeHeaderKey: key => key.toUpperCase(), + }); + + let transporterDefault: StreamTransport.Options; + transporterDefault = transporter._defaults; + + transporter.sendMail( + { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Message", + text: "I hope this message gets buffered!", + }, + (err, info) => { + err satisfies Error | null; + info satisfies StreamTransport.SentMessageInfo; + // @ts-expect-error - info is `StreamTransport.SentMessageInfo`. + info satisfies SMTPPool.SentMessageInfo; + }, + ); +} + +// Create a JSON encoded message object + +function json_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + jsonTransport: true, + skipEncoding: true, + }); + + let transporterDefault: JSONTransport.Options; + transporterDefault = transporter._defaults; + + transporter.sendMail( + { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Message", + text: "I hope this message gets buffered!", + }, + (err, info) => { + err satisfies Error | null; + info satisfies JSONTransport.SentMessageInfo; + // @ts-expect-error - info is `JSONTransport.SentMessageInfo`. + info satisfies SMTPPool.SentMessageInfo; + }, + ); +} + +// 6. Create plugins + +// 'compile' + +function plugin_compile_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + function plugin(mail: typeof transporter.MailMessage, callback: (err?: Error | null) => void) { + // if mail.data.html is a file or an url, it is returned as a Buffer + mail.resolveContent(mail.data, "html", (err, html) => { + if (err) { + callback(err); + return; + } + console.log("HTML contents: %s", html.toString()); + callback(); + }); + } + + transporter.use("compile", (mail, callback) => { + if (!mail.data.text && mail.data.html && typeof mail.data.html === "string") { + mail.data.text = mail.data.html.replace(/<[^>]*>/g, " "); + } + callback(); + }); +} + +// 'stream' + +function plugin_stream_test() { + const Transform = require("stream").Transform; + const transformer: stream.Transform = new Transform(); + + transformer._transform = function transform(chunk: Buffer, encoding, done) { + // replace all tabs with spaces in the stream chunk + for (let i = 0; i < chunk.length; i++) { + if (chunk[i] === 0x09) { + chunk[i] = 0x20; + } + } + this.push(chunk); + done(); + }; + + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport(); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + transporter.use("stream", (mail, callback) => { + // apply output transformer to the raw message stream + mail.message.transform(transformer); + callback(); + }); + + transporter.use("stream", (mail, callback) => { + const addresses = mail.message.getAddresses(); + console.log("From: %s", JSON.stringify(addresses.from)); + console.log("To: %s", JSON.stringify(addresses.to)); + console.log("Cc: %s", JSON.stringify(addresses.cc)); + console.log("Bcc: %s", JSON.stringify(addresses.bcc)); + callback(); + }); +} + +// Transport Example + +function plugin_transport_example_test() { + interface MailOptions extends Mail.Options { + mailOption?: "foo" | undefined; + } + interface Options extends MailOptions, nodemailer.TransportOptions { + transportOptions: "bar"; + } + + interface TestTransportInfo { + messageId: string; + } + class Transport implements nodemailer.Transport { + name = "minimal"; + version = "0.1.0"; + constructor(options: Options) {} + send( + mail: MailMessage, + callback: (err: Error | null, info: TestTransportInfo) => void, + ): void { + const input = mail.message.createReadStream(); + input.pipe(process.stdout); + input.on("end", () => { + callback(null, { messageId: "baz" }); + }); + } + } + class AnyTransport implements nodemailer.Transport { + name = "minimal"; + version = "0.1.0"; + constructor(options: Options) {} + send(mail: MailMessage, callback: (err: Error | null, info: any) => void): void { + const input = mail.message.createReadStream(); + input.pipe(process.stdout); + input.on("end", () => { + callback(null, { messageId: "baz" }); + }); + } + } + + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport( + new Transport({ + transportOptions: "bar", + }), + ); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + const options: MailOptions = { + from: "sender", + to: "receiver", + subject: "hello", + text: "hello world!", + mailOption: "foo", + }; + + transporter.sendMail(options); +} + +// 7. https://nodemailer.com/dkim/ + +// Sign all messages + +function dkim_sign_all_test() { + const opts: SMTPTransport.Options = { + host: "smtp.example.com", + port: 465, + secure: true, + dkim: { + domainName: "example.com", + keySelector: "2017", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + }, + }; +} + +// Sign all messages with multiple keys + +function dkim_sign_multiple_keys_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.example.com", + port: 465, + secure: true, + dkim: { + keys: [ + { + domainName: "example.com", + keySelector: "2017", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + }, + { + domainName: "example.com", + keySelector: "2016", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + }, + ], + cacheDir: false, + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// Sign a specific message + +function dkim_sign_specific_message_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.example.com", + port: 465, + secure: true, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; + + const message: Mail.Options = { + from: "sender@example.com", + to: "recipient@example.com", + subject: "Message", + text: "I hope this message gets read!", + dkim: { + domainName: "example.com", + keySelector: "2017", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + }, + }; +} + +// Cache large messages for signing + +function dkim_cache_large_messages_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.example.com", + port: 465, + secure: true, + dkim: { + domainName: "example.com", + keySelector: "2017", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + cacheDir: "/tmp", + cacheTreshold: 100 * 1024, + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// Do not sign specific header keys + +function dkim_specific_header_key_test() { + let transporter: nodemailer.Transporter; + transporter = nodemailer.createTransport({ + host: "smtp.example.com", + port: 465, + secure: true, + dkim: { + domainName: "example.com", + keySelector: "2017", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + skipFields: "message-id:date", + }, + }); + + let transporterDefault: SMTPTransport.Options; + transporterDefault = transporter._defaults; +} + +// 8. SMTP Connection + +// SMTP Connection + +function smtp_connection_test() { + const connection = new SMTPConnection({ secure: true, secured: true }); + connection.connect(err => { + if (err) throw err; + connection.login({ user: "user", pass: "pass" }, err => { + if (err) throw err; + connection.send({ from: "a@example.com", to: "b@example.net" }, "message", (err, info) => { + if (err) { + const code: string = err.code || "???"; + const response: string = err.response || "???"; + const responseCode: number = err.responseCode || 0; + const command: string = err.command || "???"; + throw err; + } + connection.reset(() => { + if (err) throw err; + connection.quit(); + connection.close(); + }); + }); + }); + }); +} + +// LMTP Connection + +function lmtp_connection_test() { + const connection = new SMTPConnection({ lmtp: true }); + connection.connect(err => { + if (err) throw err; + connection.login({ user: "user", pass: "pass" }, err => { + if (err) throw err; + connection.send({ from: "a@example.com", to: "b@example.net" }, "message", (err, info) => { + if (err) { + const code: string = err.code || "???"; + const response: string = err.response || "???"; + const responseCode: number = err.responseCode || 0; + const command: string = err.command || "???"; + throw err; + } + connection.reset(() => { + if (err) throw err; + connection.quit(); + connection.close(); + }); + }); + }); + }); +} + +// Mailcomposer + +// createReadStream + +function mailcomposer_createReadStream_test() { + const mail = new MailComposer({ from: "..." }); + const stream = mail.compile().createReadStream(); + stream.pipe(process.stdout); +} + +// build + +function mailcomposer_build_callback_test() { + const mail = new MailComposer({ from: "..." }); + mail.compile().build((err, message) => { + process.stdout.write(message); + }); +} + +async function mailcomposer_build_promise_test() { + const mail = new MailComposer({ from: "..." }); + const message = await mail.compile().build(); + process.stdout.write(message); +} + +// addressparser + +declare function isAddress(arg: unknown): arg is addressparser.Address; + +declare function isGroup(arg: unknown): arg is addressparser.Group; + +function addressparser_test() { + const input = "andris@tr.ee"; + const results: addressparser.AddressOrGroup[] = addressparser(input); + const firstResult = results[0]; + if (isAddress(firstResult)) { + const address: string = firstResult.address; + const name: string = firstResult.name; + } else if (isGroup(firstResult)) { + const group: addressparser.Address[] = firstResult.group; + const name: string = firstResult.name; + } +} + +function addressparser_flatten_test() { + const input = "andris@tr.ee"; + const results = addressparser(input, { flatten: true }); + const firstResult = results[0]; + const address: string = firstResult.address; + const name: string = firstResult.name; +} + +// base64 + +function base64_test() { + base64.encode("abcd= ÕÄÖÜ"); + + base64.encode(new Buffer([0x00, 0x01, 0x02, 0x20, 0x03])); +} + +// dkim + +function dkim_test_options() { + const dkim = new DKIM({ + domainName: "example.com", + keySelector: "2017", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + }); + const stream = dkim.sign("Message"); + stream.pipe(process.stdout); +} + +function dkim_test_extra_options() { + const dkim = new DKIM(); + const stream = dkim.sign("Message", { + domainName: "example.com", + keySelector: "2017", + privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...", + }); + stream.pipe(process.stdout); +} + +// fetch + +function fetch_test() { + const stream = fetch("http://localhost/"); + + const statusCode: number = stream.statusCode; + const headers = stream.headers; + const contentType: string | undefined = headers["content-type"]; + + fetch("http://localhost:/", { + allowErrorResponse: true, + method: "post", + cookie: "test=pest", + body: { + hello: "world 😭", + another: "value", + }, + timeout: 1000, + tls: { + rejectUnauthorized: true, + }, + }); +} + +// fetch/cookies + +function fetch_cookies_test() { + const biskviit = new Cookies(); + + biskviit.getPath("/"); + + biskviit.isExpired({ + name: "a", + value: "b", + expires: new Date(Date.now() + 10000), + }); + + biskviit.compare( + { + name: "zzz", + path: "/", + domain: "example.com", + secure: false, + httponly: false, + }, + { + name: "zzz", + path: "/", + domain: "example.com", + secure: false, + httponly: false, + }, + ); + + biskviit.add({ + name: "zzz", + value: "abc", + path: "/", + expires: new Date(Date.now() + 10000), + domain: "example.com", + secure: false, + httponly: false, + }); + + const cookie = { + name: "zzz", + value: "abc", + path: "/def/", + expires: new Date(Date.now() + 10000), + domain: "example.com", + secure: false, + httponly: false, + }; + + biskviit.match(cookie, "http://example.com/def/"); + + biskviit.parse("theme=plain"); + + biskviit.list("https://www.foo.com"); + + biskviit.get("https://www.foo.com"); + + biskviit.set("theme=plain", "https://foo.com/"); +} + +// mime-funcs + +function mime_funcs_test() { + mimeFuncs.isPlainText("abc"); + + mimeFuncs.hasLongerLines("abc\ndef", 5); + + mimeFuncs.encodeWord("See on õhin test"); + mimeFuncs.encodeWord("See on õhin test", "B"); + mimeFuncs.encodeWords("метель\" вьюга", "Q", 52); + mimeFuncs.encodeWords("Jõgeva Jõgeva Jõgeva mugeva Jõgeva Jõgeva Jõgeva Jõgeva Jõgeva", "Q", 16); + mimeFuncs.encodeWords("õõõõõ õõõõõ õõõõõ mugeva õõõõõ õõõõõ õõõõõ õõõõõ Jõgeva", "B", 30); + + mimeFuncs.buildHeaderParam("title", "this is just a title", 500); + + const parsedHeader = mimeFuncs.parseHeaderValue("content-disposition: attachment; filename=filename"); + console.log(parsedHeader.params.filename); + + mimeFuncs.buildHeaderValue({ + value: "test", + }); + + mimeFuncs.buildHeaderValue({ + value: "test", + params: { + a: "b", + }, + }); + + mimeFuncs.foldLines("Testin command line", 76, true); + mimeFuncs.foldLines("Testin command line", 76); +} + +// mime-node + +function mime_node_test() { + let mb: MimeNode; + + // constructor + { + mb = new MimeNode(); + + mb = new MimeNode("text/plain"); + + mb = new MimeNode("text/plain", {}); + + mb = new MimeNode("text/plain", { + rootNode: mb, + parentNode: mb, + filename: "filename", + hostname: "hostname", + baseBoundary: "baseBoundary", + keepBcc: true, + newline: "win", + normalizeHeaderKey: key => key.toUpperCase(), + boundaryPrefix: "boundaryPrefix", + disableFileAccess: true, + disableUrlAccess: true, + }); + + mb = new MimeNode("text/plain", { textEncoding: "B" }); + mb = new MimeNode("text/plain", { textEncoding: "Q" }); + } + + const child = mb.createChild("multipart/mixed"); + mb = mb.appendChild(child); + mb = child.replace(child); + + mb = mb.setHeader("key", "value"); + + // $ExpectType string + mb.getHeader("key"); + + mb = mb.addHeader("key", "value1"); + mb = mb.addHeader({ + key: "value4", + key2: "value5", + }); + + mb = mb.setHeader([ + { + key: "key", + value: "value2", + }, + { + key: "key2", + value: "value3", + }, + ]); + + mb = mb.setHeader("key", ["value1", "value2", "value3"]); + + mb = mb.setContent("abc"); + + // $ExpectType void + mb.build((err, buf) => { + // $ExpectType Error | null + err; + // $ExpectType Buffer || Buffer + buf; + }); + + // $ExpectType Promise || Promise> + mb.build(); + + // $ExpectType string + mb.getTransferEncoding(); + + // $ExpectType string + mb.buildHeaders(); + + { + // $ExpectType Readable + mb.createReadStream(); + + const options: stream.ReadableOptions = {}; + // $ExpectType Readable + mb.createReadStream(options); + } + + // $ExpectType void + mb.transform(new stream.Transform()); + + // $ExpectType void + mb.processFunc(input => { + // $ExpectType Readable + input; + return input; + }); + + { + const outputStream = new stream.Readable(); + const options: stream.ReadableOptions = {}; + + // $ExpectType void + mb.stream(outputStream, options, err => { + // $ExpectType Error | null | undefined + err; + }); + } + + { + let envelope: Mail.Envelope = {}; + mb = mb.setEnvelope(envelope); + } + + { + const addresses = mb.getAddresses(); + + // $ExpectType string[] | undefined + addresses.bcc; + // $ExpectType string[] | undefined + addresses.cc; + // $ExpectType string[] | undefined + addresses.from; + // $ExpectType string[] | undefined + addresses["reply-to"]; + // $ExpectType string[] | undefined + addresses.sender; + // $ExpectType string[] | undefined + addresses.to; + } + + { + const envelope = mb.getEnvelope(); + + // $ExpectType string | false + envelope.from; + // $ExpectType string[] + envelope.to; + } + + // $ExpectType string + mb.messageId(); + + { + mb = mb.setRaw("raw"); + mb = mb.setRaw(Buffer.from("")); + mb = mb.setRaw(new stream.Readable()); + } + + // $ExpectType string + mb.baseBoundary; + + // $ExpectType string | false | undefined + mb.boundary; + + // $ExpectType string + mb.boundaryPrefix; + + // $ExpectType MimeNode[] + mb.childNodes; + + // $ExpectType string | Buffer | Readable | undefined || string | Buffer | Readable | undefined + mb.content; + + // $ExpectType string | undefined + mb.contentType; + + // $ExpectType Date + mb.date; + + // $ExpectType boolean + mb.disableFileAccess; + + // $ExpectType boolean + mb.disableUrlAccess; + + // $ExpectType string | undefined + mb.filename; + + // $ExpectType string | undefined + mb.hostname; + + // $ExpectType boolean + mb.keepBcc; + + // $ExpectType boolean | undefined + mb.multipart; + + // $ExpectType string | undefined + mb.newline; + + // $ExpectType number + mb.nodeCounter; + + // $ExpectType ((key: string) => string) | undefined + mb.normalizeHeaderKey; + + // $ExpectType MimeNode | undefined + mb.parentNode; + + // $ExpectType MimeNode + mb.rootNode; + + // $ExpectType "B" | "Q" | "" + mb.textEncoding; +} + +// mime-types + +function mime_types_test() { + mimeTypes.detectExtension(false); + mimeTypes.detectExtension("unknown"); + + mimeTypes.detectMimeType(false); + mimeTypes.detectMimeType("unknown"); +} + +// qp + +function qp_test() { + qp.encode("abcd= ÕÄÖÜ"); + + qp.encode(new Buffer([0x00, 0x01, 0x02, 0x20, 0x03])); +} + +// shared + +function shared_getLogger_test() { + shared.getLogger({ + logger: false, + }); + + shared.getLogger(); + + const options = shared.parseConnectionUrl( + "smtps://user:pass@localhost:123?tls.rejectUnauthorized=false&name=horizon", + ); + console.log(options.secure, options.auth!.user, options.tls!.rejectUnauthorized); +} + +function shared_resolveContent_string_test() { + const mail = { + data: { + html: "

Tere, tere

vana kere!

\n", + }, + }; + + shared.resolveContent(mail.data, "html", (err, value) => { + if (!err) { + console.log(value); + } + }); + + shared.resolveContent(mail.data, "html").then(value => console.log(value)); +} + +function shared_resolveContent_buffer_test() { + const mail = { + data: { + html: new Buffer("

Tere, tere

vana kere!

\n"), + }, + }; + + shared.resolveContent(mail.data, "html", (err, value) => { + if (!err) { + console.log(value); + } + }); + + shared.resolveContent(mail.data, "html").then(value => console.log(value)); +} + +function shared_assign_test() { + const target = { + a: 1, + b: 2, + c: 3, + }; + const arg1 = { + b: 5, + y: 66, + e: 33, + }; + + const arg2 = { + y: 17, + qq: 98, + }; + + shared.assign(target, arg1, arg2); +} + +function shared_encodeXText_test() { + shared.encodeXText("teretere"); +} + +// well-known + +function well_known_test() { + const options = wellKnown("Gmail"); + if (options) { + console.log(options.host, options.port, options.secure); + } +} + +// xoauth2 + +function xoauth2_test() { + const xoauth2 = new XOAuth2({ + user: "test@example.com", + clientId: "{Client ID}", + clientSecret: "{Client Secret}", + refreshToken: "saladus", + accessUrl: "http://localhost:8993/", + accessToken: "abc", + timeout: 3600, + }); + xoauth2.getToken(false, (err, accessToken) => { + if (err) throw err; + const token: string = xoauth2.buildXOAuth2Token(accessToken); + }); +} + +function xoauth2_sign_payload_test() { + const xoauth2 = new XOAuth2({ + user: "test@example.com", + serviceClient: "{Client ID}", + accessUrl: "http://localhost:8497/", + timeout: 3600, + privateKey: "-----BEGIN RSA PRIVATE KEY-----\n...", + }); + xoauth2.jwtSignRS256({ + some: "payload", + }); +} + +// testSendMailOverloads +(async () => { + const DISABLE_EMAILS = false; + + const transporter = DISABLE_EMAILS + ? nodemailer.createTransport({ + streamTransport: true, + buffer: true, + }) + : nodemailer.createTransport({ + host: "localhost", + port: 25, + }); + + await transporter.sendMail({ + from: "sender@example.com", + to: "recipient@example.com", + subject: "Buffered message", + text: "This message is buffered.", + }); +}); diff --git a/types/nodemailer/v7/package.json b/types/nodemailer/v7/package.json new file mode 100644 index 00000000000000..9774a4ca4d8a55 --- /dev/null +++ b/types/nodemailer/v7/package.json @@ -0,0 +1,30 @@ +{ + "private": true, + "name": "@types/nodemailer", + "version": "7.0.9999", + "projects": [ + "https://github.com/nodemailer/nodemailer", + "https://nodemailer.com" + ], + "dependencies": { + "@types/node": "*" + }, + "devDependencies": { + "@aws-sdk/client-sesv2": "^3.985.0", + "@types/nodemailer": "workspace:." + }, + "owners": [ + { + "name": "Rogier Schouten", + "githubUsername": "rogierschouten" + }, + { + "name": "Piotr Roszatycki", + "githubUsername": "dex4er" + }, + { + "name": "Daniel Chao", + "githubUsername": "bioball" + } + ] +} diff --git a/types/nodemailer/v7/tsconfig.json b/types/nodemailer/v7/tsconfig.json new file mode 100644 index 00000000000000..9781d783310670 --- /dev/null +++ b/types/nodemailer/v7/tsconfig.json @@ -0,0 +1,50 @@ +{ + "compilerOptions": { + "module": "node16", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "lib/addressparser/index.d.ts", + "lib/base64/index.d.ts", + "lib/dkim/index.d.ts", + "lib/dkim/message-parser.d.ts", + "lib/dkim/relaxed-body.d.ts", + "lib/dkim/sign.d.ts", + "lib/fetch/cookies.d.ts", + "lib/fetch/index.d.ts", + "lib/json-transport/index.d.ts", + "lib/mail-composer/index.d.ts", + "lib/mailer/index.d.ts", + "lib/mailer/mail-message.d.ts", + "lib/mime-funcs/index.d.ts", + "lib/mime-funcs/mime-types.d.ts", + "lib/mime-node/index.d.ts", + "lib/mime-node/last-newline.d.ts", + "lib/qp/index.d.ts", + "lib/sendmail-transport/index.d.ts", + "lib/sendmail-transport/le-unix.d.ts", + "lib/sendmail-transport/le-windows.d.ts", + "lib/ses-transport/index.d.ts", + "lib/shared/index.d.ts", + "lib/smtp-connection/data-stream.d.ts", + "lib/smtp-connection/http-proxy-client.d.ts", + "lib/smtp-connection/index.d.ts", + "lib/smtp-pool/index.d.ts", + "lib/smtp-pool/pool-resource.d.ts", + "lib/smtp-transport/index.d.ts", + "lib/stream-transport/index.d.ts", + "lib/well-known/index.d.ts", + "lib/xoauth2/index.d.ts", + "nodemailer-tests.ts" + ] +}