mirror of https://github.com/eleith/emailjs.git
Compare commits
14 Commits
f2e53576bc
...
1e28b13f21
Author | SHA1 | Date |
---|---|---|
Zack Schuster | 1e28b13f21 | |
Zack Schuster | abe9c101a1 | |
Zack Schuster | 67d56d13c2 | |
Zack Schuster | 41933468e5 | |
Zack Schuster | db721304e1 | |
Zack Schuster | 025af66e87 | |
Zack Schuster | f5426698a7 | |
Zack Schuster | 57c5258b25 | |
Zack Schuster | 00ccfacb7c | |
Zack Schuster | 735217e170 | |
Zack Schuster | f419b6f169 | |
Zack Schuster | 7f8e15ddb6 | |
Zack Schuster | 7d772326d9 | |
Zack Schuster | a395f862ec |
|
@ -9,12 +9,6 @@
|
||||||
"plugin:@typescript-eslint/recommended"
|
"plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-explicit-any": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ignoreRestArgs": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"curly": [
|
"curly": [
|
||||||
"error",
|
"error",
|
||||||
"all"
|
"all"
|
||||||
|
|
|
@ -7,7 +7,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [^12, ^14, ^16, ^17]
|
node: [^12, ^14, ^16, ^18]
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -27,10 +27,10 @@
|
||||||
"eslint": "8.14.0",
|
"eslint": "8.14.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-prettier": "4.0.0",
|
"eslint-plugin-prettier": "4.0.0",
|
||||||
"mailparser": "3.4.0",
|
"mailparser": "3.5.0",
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"rollup": "2.70.2",
|
"rollup": "2.70.2",
|
||||||
"smtp-server": "3.10.0",
|
"smtp-server": "3.11.0",
|
||||||
"ts-node": "10.7.0",
|
"ts-node": "10.7.0",
|
||||||
"tslib": "2.4.0",
|
"tslib": "2.4.0",
|
||||||
"typescript": "4.3.5"
|
"typescript": "4.3.5"
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"nodemailer": "6.7.3"
|
"nodemailer": "6.7.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
|
|
@ -54,17 +54,7 @@ export class SMTPClient {
|
||||||
msg: T,
|
msg: T,
|
||||||
callback: MessageCallback<T>
|
callback: MessageCallback<T>
|
||||||
): void {
|
): void {
|
||||||
const message =
|
const message = msg instanceof Message ? msg : new Message(msg);
|
||||||
msg instanceof Message
|
|
||||||
? msg
|
|
||||||
: this._canMakeMessage(msg)
|
|
||||||
? new Message(msg)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (message == null) {
|
|
||||||
callback(new Error('message is not a valid Message instance'), msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { isValid, validationError } = message.checkValidity();
|
const { isValid, validationError } = message.checkValidity();
|
||||||
|
|
||||||
|
@ -189,13 +179,9 @@ export class SMTPClient {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
protected _connect(stack: MessageStack) {
|
protected _connect(stack: MessageStack) {
|
||||||
/**
|
const connect = (err: Error | null) => {
|
||||||
* @param {Error} err callback error
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const connect = (err: Error) => {
|
|
||||||
if (!err) {
|
if (!err) {
|
||||||
const begin = (err: Error) => {
|
const begin = (err: Error | null) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
this._poll();
|
this._poll();
|
||||||
|
@ -226,19 +212,6 @@ export class SMTPClient {
|
||||||
this.smtp.connect(connect);
|
this.smtp.connect(connect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @protected
|
|
||||||
* @param {MessageStack} msg message stack
|
|
||||||
* @returns {boolean} can make message
|
|
||||||
*/
|
|
||||||
protected _canMakeMessage(msg: MessageHeaders) {
|
|
||||||
return (
|
|
||||||
msg.from &&
|
|
||||||
(msg.to || msg.cc || msg.bcc) &&
|
|
||||||
(msg.text !== undefined || this._containsInlinedHtml(msg.attachment))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* @param {MessageAttachment | MessageAttachment[]} attachment attachment
|
* @param {MessageAttachment | MessageAttachment[]} attachment attachment
|
||||||
|
@ -276,11 +249,7 @@ export class SMTPClient {
|
||||||
* @returns {function(Error): void} callback
|
* @returns {function(Error): void} callback
|
||||||
*/
|
*/
|
||||||
protected _sendsmtp(stack: MessageStack, next: (msg: MessageStack) => void) {
|
protected _sendsmtp(stack: MessageStack, next: (msg: MessageStack) => void) {
|
||||||
/**
|
return (err: Error | null) => {
|
||||||
* @param {Error} [err] error
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
return (err: Error) => {
|
|
||||||
if (!err && next) {
|
if (!err && next) {
|
||||||
next.apply(this, [stack]);
|
next.apply(this, [stack]);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -40,10 +40,10 @@ const GREYLIST_DELAY = 300 as const;
|
||||||
let DEBUG: 0 | 1 = 0;
|
let DEBUG: 0 | 1 = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {...any[]} args the message(s) to log
|
* @param {...unknown[]} args the message(s) to log
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const log = (...args: any[]) => {
|
const log = (...args: unknown[]) => {
|
||||||
if (DEBUG === 1) {
|
if (DEBUG === 1) {
|
||||||
args.forEach((d) =>
|
args.forEach((d) =>
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -57,22 +57,20 @@ const log = (...args: any[]) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {function(...any[]): void} callback the function to call
|
|
||||||
* @param {...any[]} args the arguments to apply to the function
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const caller = (callback?: (...rest: any[]) => void, ...args: any[]) => {
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
callback(...args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SMTPSocketOptions = Omit<
|
export type SMTPSocketOptions = Omit<
|
||||||
ConnectionOptions,
|
ConnectionOptions,
|
||||||
'port' | 'host' | 'path' | 'socket' | 'timeout' | 'secureContext'
|
'port' | 'host' | 'path' | 'socket' | 'timeout' | 'secureContext'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type SMTPCommandCallback = (
|
||||||
|
err: Error | SMTPError | null,
|
||||||
|
data?:
|
||||||
|
| string
|
||||||
|
| { code: string | number; data: string; message: string }
|
||||||
|
| null,
|
||||||
|
message?: string
|
||||||
|
) => void;
|
||||||
|
|
||||||
export interface SMTPConnectionOptions {
|
export interface SMTPConnectionOptions {
|
||||||
timeout: number | null;
|
timeout: number | null;
|
||||||
user: string;
|
user: string;
|
||||||
|
@ -83,7 +81,7 @@ export interface SMTPConnectionOptions {
|
||||||
ssl: boolean | SMTPSocketOptions;
|
ssl: boolean | SMTPSocketOptions;
|
||||||
tls: boolean | SMTPSocketOptions;
|
tls: boolean | SMTPSocketOptions;
|
||||||
authentication: (keyof typeof AUTH_METHODS)[];
|
authentication: (keyof typeof AUTH_METHODS)[];
|
||||||
logger: (...args: any[]) => void;
|
logger: (...args: unknown[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConnectOptions {
|
export interface ConnectOptions {
|
||||||
|
@ -116,7 +114,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
protected tls: boolean | SMTPSocketOptions = false;
|
protected tls: boolean | SMTPSocketOptions = false;
|
||||||
protected port: number;
|
protected port: number;
|
||||||
|
|
||||||
private greylistResponseTracker = new WeakSet<(...rest: any[]) => void>();
|
private greylistResponseTracker = new WeakSet<SMTPCommandCallback>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP class written using python's (2.7) smtplib.py as a base.
|
* SMTP class written using python's (2.7) smtplib.py as a base.
|
||||||
|
@ -198,7 +196,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @returns {SMTPState} the current state
|
* @returns {0 | 1 | 2} the current state
|
||||||
*/
|
*/
|
||||||
public state() {
|
public state() {
|
||||||
return this._state;
|
return this._state;
|
||||||
|
@ -218,14 +216,14 @@ export class SMTPConnection extends EventEmitter {
|
||||||
* NOTE: `host` is trimmed before being used to establish a connection; however, the original untrimmed value will still be visible in configuration.
|
* NOTE: `host` is trimmed before being used to establish a connection; however, the original untrimmed value will still be visible in configuration.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {number} [port] the port to use for the connection
|
* @param {number} [port] the port to use for the connection
|
||||||
* @param {string} [host] the hostname to use for the connection
|
* @param {string} [host] the hostname to use for the connection
|
||||||
* @param {ConnectOptions} [options={}] the options
|
* @param {ConnectOptions} [options={}] the options
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public connect(
|
public connect(
|
||||||
callback: (...rest: any[]) => void,
|
callback: SMTPCommandCallback,
|
||||||
port: number = this.port,
|
port: number = this.port,
|
||||||
host: string = this.host,
|
host: string = this.host,
|
||||||
options: ConnectOptions = {}
|
options: ConnectOptions = {}
|
||||||
|
@ -252,8 +250,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
!this.sock.authorized
|
!this.sock.authorized
|
||||||
) {
|
) {
|
||||||
this.close(true);
|
this.close(true);
|
||||||
caller(
|
callback(
|
||||||
callback,
|
|
||||||
SMTPError.create(
|
SMTPError.create(
|
||||||
'could not establish an ssl connection',
|
'could not establish an ssl connection',
|
||||||
SMTPErrorStates.CONNECTIONAUTH
|
SMTPErrorStates.CONNECTIONAUTH
|
||||||
|
@ -275,8 +272,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
} else {
|
} else {
|
||||||
this.close(true);
|
this.close(true);
|
||||||
this.log(err);
|
this.log(err);
|
||||||
caller(
|
callback(
|
||||||
callback,
|
|
||||||
SMTPError.create(
|
SMTPError.create(
|
||||||
'could not connect',
|
'could not connect',
|
||||||
SMTPErrorStates.COULDNOTCONNECT,
|
SMTPErrorStates.COULDNOTCONNECT,
|
||||||
|
@ -295,18 +291,17 @@ export class SMTPConnection extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.close(true);
|
this.close(true);
|
||||||
caller(callback, err);
|
callback(err);
|
||||||
} else if (msg.code == '220') {
|
} else if (msg.code == '220') {
|
||||||
this.log(msg.data);
|
this.log(msg.data);
|
||||||
|
|
||||||
// might happen first, so no need to wait on connected()
|
// might happen first, so no need to wait on connected()
|
||||||
this._state = SMTPState.CONNECTED;
|
this._state = SMTPState.CONNECTED;
|
||||||
caller(callback, null, msg.data);
|
callback(null, msg.data);
|
||||||
} else {
|
} else {
|
||||||
this.log(`response (data): ${msg.data}`);
|
this.log(`response (data): ${msg.data}`);
|
||||||
this.quit(() => {
|
this.quit(() => {
|
||||||
caller(
|
callback(
|
||||||
callback,
|
|
||||||
SMTPError.create(
|
SMTPError.create(
|
||||||
'bad response on connection',
|
'bad response on connection',
|
||||||
SMTPErrorStates.BADRESPONSE,
|
SMTPErrorStates.BADRESPONSE,
|
||||||
|
@ -343,19 +338,19 @@ export class SMTPConnection extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {string} str the string to send
|
* @param {string} str the string to send
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public send(str: string, callback: (...args: any[]) => void) {
|
public send(str: string, callback: SMTPCommandCallback) {
|
||||||
if (this.sock != null && this._state === SMTPState.CONNECTED) {
|
if (this.sock != null && this._state === SMTPState.CONNECTED) {
|
||||||
this.log(str);
|
this.log(str);
|
||||||
|
|
||||||
this.sock.once('response', (err, msg) => {
|
this.sock.once('response', (err, msg) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
caller(callback, err);
|
callback(err, undefined);
|
||||||
} else {
|
} else {
|
||||||
this.log(msg.data);
|
this.log(msg.data);
|
||||||
caller(callback, null, msg);
|
callback(null, msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (this.sock.writable) {
|
if (this.sock.writable) {
|
||||||
|
@ -363,12 +358,12 @@ export class SMTPConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.close(true);
|
this.close(true);
|
||||||
caller(
|
callback(
|
||||||
callback,
|
|
||||||
SMTPError.create(
|
SMTPError.create(
|
||||||
'no connection has been established',
|
'no connection has been established',
|
||||||
SMTPErrorStates.NOCONNECTION
|
SMTPErrorStates.NOCONNECTION
|
||||||
)
|
),
|
||||||
|
undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,13 +371,13 @@ export class SMTPConnection extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {string} cmd command to issue
|
* @param {string} cmd command to issue
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {(number[] | number)} [codes=[250]] array codes
|
* @param {(number[] | number)} [codes=[250]] array codes
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public command(
|
public command(
|
||||||
cmd: string,
|
cmd: string,
|
||||||
callback: (...rest: any[]) => void,
|
callback: SMTPCommandCallback,
|
||||||
codes: number[] | number = [250]
|
codes: number[] | number = [250]
|
||||||
) {
|
) {
|
||||||
const codesArray = Array.isArray(codes)
|
const codesArray = Array.isArray(codes)
|
||||||
|
@ -391,16 +386,22 @@ export class SMTPConnection extends EventEmitter {
|
||||||
? [codes]
|
? [codes]
|
||||||
: [250];
|
: [250];
|
||||||
|
|
||||||
const response = (
|
const response: SMTPCommandCallback = (err, msg) => {
|
||||||
err: Error | null | undefined,
|
|
||||||
msg: { code: string | number; data: string; message: string }
|
|
||||||
) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
caller(callback, err);
|
callback(err);
|
||||||
|
} else if (msg == null || typeof msg === 'string') {
|
||||||
|
callback(
|
||||||
|
SMTPError.create(
|
||||||
|
'Invalid message in response',
|
||||||
|
SMTPErrorStates.BADRESPONSE,
|
||||||
|
err,
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const code = Number(msg.code);
|
const code = Number(msg.code);
|
||||||
if (codesArray.indexOf(code) !== -1) {
|
if (codesArray.indexOf(code) !== -1) {
|
||||||
caller(callback, err, msg.data, msg.message);
|
callback(err, msg.data, msg.message);
|
||||||
} else if (
|
} else if (
|
||||||
(code === 450 || code === 451) &&
|
(code === 450 || code === 451) &&
|
||||||
msg.message.toLowerCase().includes('greylist') &&
|
msg.message.toLowerCase().includes('greylist') &&
|
||||||
|
@ -415,8 +416,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
const errorMessage = `bad response on command '${
|
const errorMessage = `bad response on command '${
|
||||||
cmd.split(' ')[0]
|
cmd.split(' ')[0]
|
||||||
}'${suffix}`;
|
}'${suffix}`;
|
||||||
caller(
|
callback(
|
||||||
callback,
|
|
||||||
SMTPError.create(
|
SMTPError.create(
|
||||||
errorMessage,
|
errorMessage,
|
||||||
SMTPErrorStates.BADRESPONSE,
|
SMTPErrorStates.BADRESPONSE,
|
||||||
|
@ -442,35 +442,35 @@ export class SMTPConnection extends EventEmitter {
|
||||||
* As this command was deprecated by rfc2821, it should only be used for compatibility with non-compliant servers.
|
* As this command was deprecated by rfc2821, it should only be used for compatibility with non-compliant servers.
|
||||||
* @see https://tools.ietf.org/html/rfc2821#appendix-F.3
|
* @see https://tools.ietf.org/html/rfc2821#appendix-F.3
|
||||||
*
|
*
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {string} domain the domain to associate with the 'helo' request
|
* @param {string} domain the domain to associate with the 'helo' request
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public helo(callback: (...rest: any[]) => void, domain?: string) {
|
public helo(callback: SMTPCommandCallback, domain?: string) {
|
||||||
this.command(`helo ${domain || this.domain}`, (err, data) => {
|
this.command(`helo ${domain || this.domain}`, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
caller(callback, err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
this.parse_smtp_features(data);
|
this.parse_smtp_features(data as string);
|
||||||
caller(callback, err, data);
|
callback(err, data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public starttls(callback: (...rest: any[]) => void) {
|
public starttls(callback: SMTPCommandCallback) {
|
||||||
const response = (err: Error, msg: { data: unknown }) => {
|
const response: SMTPCommandCallback = (err, msg) => {
|
||||||
if (this.sock == null) {
|
if (this.sock == null) {
|
||||||
throw new Error('null socket');
|
throw new Error('null socket');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
err.message += ' while establishing a starttls session';
|
err.message += ' while establishing a starttls session';
|
||||||
caller(callback, err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
const secureContext = createSecureContext(
|
const secureContext = createSecureContext(
|
||||||
typeof this.tls === 'object' ? this.tls : {}
|
typeof this.tls === 'object' ? this.tls : {}
|
||||||
|
@ -479,7 +479,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
|
|
||||||
secureSocket.on('error', (err: Error) => {
|
secureSocket.on('error', (err: Error) => {
|
||||||
this.close(true);
|
this.close(true);
|
||||||
caller(callback, err);
|
callback(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._secure = true;
|
this._secure = true;
|
||||||
|
@ -488,7 +488,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
new SMTPResponseMonitor(this.sock, this.timeout, () =>
|
new SMTPResponseMonitor(this.sock, this.timeout, () =>
|
||||||
this.close(true)
|
this.close(true)
|
||||||
);
|
);
|
||||||
caller(callback, msg.data);
|
callback(null, typeof msg === 'string' ? msg : msg?.data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -527,22 +527,22 @@ export class SMTPConnection extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {string} domain the domain to associate with the 'ehlo' request
|
* @param {string} domain the domain to associate with the 'ehlo' request
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public ehlo(callback: (...rest: any[]) => void, domain?: string) {
|
public ehlo(callback: SMTPCommandCallback, domain?: string) {
|
||||||
this.features = {};
|
this.features = {};
|
||||||
this.command(`ehlo ${domain || this.domain}`, (err, data) => {
|
this.command(`ehlo ${domain || this.domain}`, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
caller(callback, err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
this.parse_smtp_features(data);
|
this.parse_smtp_features(data as string);
|
||||||
|
|
||||||
if (this.tls && !this._secure) {
|
if (this.tls && !this._secure) {
|
||||||
this.starttls(() => this.ehlo(callback, domain));
|
this.starttls(() => this.ehlo(callback, domain));
|
||||||
} else {
|
} else {
|
||||||
caller(callback, err, data);
|
callback(err, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -560,67 +560,67 @@ export class SMTPConnection extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @description SMTP 'help' command, returns text from the server
|
* @description SMTP 'help' command, returns text from the server
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {string} domain the domain to associate with the 'help' request
|
* @param {string} domain the domain to associate with the 'help' request
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public help(callback: (...rest: any[]) => void, domain: string) {
|
public help(callback: SMTPCommandCallback, domain: string) {
|
||||||
this.command(domain ? `help ${domain}` : 'help', callback, [211, 214]);
|
this.command(domain ? `help ${domain}` : 'help', callback, [211, 214]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public rset(callback: (...rest: any[]) => void) {
|
public rset(callback: SMTPCommandCallback) {
|
||||||
this.command('rset', callback);
|
this.command('rset', callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public noop(callback: (...rest: any[]) => void) {
|
public noop(callback: SMTPCommandCallback) {
|
||||||
this.send('noop', callback);
|
this.send('noop', callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {string} from the sender
|
* @param {string} from the sender
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public mail(callback: (...rest: any[]) => void, from: string) {
|
public mail(callback: SMTPCommandCallback, from: string) {
|
||||||
this.command(`mail FROM:${from}`, callback);
|
this.command(`mail FROM:${from}`, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {string} to the receiver
|
* @param {string} to the receiver
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public rcpt(callback: (...rest: any[]) => void, to: string) {
|
public rcpt(callback: SMTPCommandCallback, to: string) {
|
||||||
this.command(`RCPT TO:${to}`, callback, [250, 251]);
|
this.command(`RCPT TO:${to}`, callback, [250, 251]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public data(callback: (...rest: any[]) => void) {
|
public data(callback: SMTPCommandCallback) {
|
||||||
this.command('data', callback, [354]);
|
this.command('data', callback, [354]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public data_end(callback: (...rest: any[]) => void) {
|
public data_end(callback: SMTPCommandCallback) {
|
||||||
this.command(`${CRLF}.`, callback);
|
this.command(`${CRLF}.`, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -638,10 +638,10 @@ export class SMTPConnection extends EventEmitter {
|
||||||
* @public
|
* @public
|
||||||
* @description SMTP 'verify' command -- checks for address validity.
|
* @description SMTP 'verify' command -- checks for address validity.
|
||||||
* @param {string} address the address to validate
|
* @param {string} address the address to validate
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public verify(address: string, callback: (...rest: any[]) => void) {
|
public verify(address: string, callback: SMTPCommandCallback) {
|
||||||
this.command(`vrfy ${address}`, callback, [250, 251, 252]);
|
this.command(`vrfy ${address}`, callback, [250, 251, 252]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,10 +649,10 @@ export class SMTPConnection extends EventEmitter {
|
||||||
* @public
|
* @public
|
||||||
* @description SMTP 'expn' command -- expands a mailing list.
|
* @description SMTP 'expn' command -- expands a mailing list.
|
||||||
* @param {string} address the mailing list to expand
|
* @param {string} address the mailing list to expand
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public expn(address: string, callback: (...rest: any[]) => void) {
|
public expn(address: string, callback: SMTPCommandCallback) {
|
||||||
this.command(`expn ${address}`, callback);
|
this.command(`expn ${address}`, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -663,23 +663,20 @@ export class SMTPConnection extends EventEmitter {
|
||||||
* If there has been no previous EHLO or HELO command self session, self
|
* If there has been no previous EHLO or HELO command self session, self
|
||||||
* method tries ESMTP EHLO first.
|
* method tries ESMTP EHLO first.
|
||||||
*
|
*
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {string} [domain] the domain to associate with the command
|
* @param {string} [domain] the domain to associate with the command
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public ehlo_or_helo_if_needed(
|
public ehlo_or_helo_if_needed(
|
||||||
callback: (...rest: any[]) => void,
|
callback: SMTPCommandCallback,
|
||||||
domain?: string
|
domain?: string
|
||||||
) {
|
) {
|
||||||
// is this code callable...?
|
|
||||||
if (!this.features) {
|
if (!this.features) {
|
||||||
const response = (err: Error, data: unknown) =>
|
|
||||||
caller(callback, err, data);
|
|
||||||
this.ehlo((err, data) => {
|
this.ehlo((err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.helo(response, domain);
|
this.helo(callback, domain);
|
||||||
} else {
|
} else {
|
||||||
caller(callback, err, data);
|
callback(err, data);
|
||||||
}
|
}
|
||||||
}, domain);
|
}, domain);
|
||||||
}
|
}
|
||||||
|
@ -695,14 +692,14 @@ export class SMTPConnection extends EventEmitter {
|
||||||
*
|
*
|
||||||
* This method will return normally if the authentication was successful.
|
* This method will return normally if the authentication was successful.
|
||||||
*
|
*
|
||||||
* @param {function(...any[]): void} callback function to call after response
|
* @param {SMTPCommandCallback} callback function to call after response
|
||||||
* @param {string} [user] the username to authenticate with
|
* @param {string} [user] the username to authenticate with
|
||||||
* @param {string} [password] the password for the authentication
|
* @param {string} [password] the password for the authentication
|
||||||
* @param {{ method: string, domain: string }} [options] login options
|
* @param {{ method: string, domain: string }} [options] login options
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public login(
|
public login(
|
||||||
callback: (...rest: any[]) => void,
|
callback: SMTPCommandCallback,
|
||||||
user?: string,
|
user?: string,
|
||||||
password?: string,
|
password?: string,
|
||||||
options: { method?: string; domain?: string } = {}
|
options: { method?: string; domain?: string } = {}
|
||||||
|
@ -717,7 +714,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
|
|
||||||
const initiate = (err: Error | null | undefined, data: unknown) => {
|
const initiate = (err: Error | null | undefined, data: unknown) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
caller(callback, err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,8 +776,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
const failed = (err: Error, data: unknown) => {
|
const failed = (err: Error, data: unknown) => {
|
||||||
this.loggedin = false;
|
this.loggedin = false;
|
||||||
this.close(); // if auth is bad, close the connection, it won't get better by itself
|
this.close(); // if auth is bad, close the connection, it won't get better by itself
|
||||||
caller(
|
callback(
|
||||||
callback,
|
|
||||||
SMTPError.create(
|
SMTPError.create(
|
||||||
'authorization.failed',
|
'authorization.failed',
|
||||||
SMTPErrorStates.AUTHFAILED,
|
SMTPErrorStates.AUTHFAILED,
|
||||||
|
@ -790,36 +786,21 @@ export class SMTPConnection extends EventEmitter {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const response: SMTPCommandCallback = (err, data) => {
|
||||||
* @param {Error} err err
|
|
||||||
* @param {unknown} data data
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const response = (err: Error | null | undefined, data: unknown) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
failed(err, data);
|
failed(err, data);
|
||||||
} else {
|
} else {
|
||||||
this.loggedin = true;
|
this.loggedin = true;
|
||||||
caller(callback, err, data);
|
callback(err, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const attempt: SMTPCommandCallback = (err, data, msg) => {
|
||||||
* @param {Error} err err
|
|
||||||
* @param {unknown} data data
|
|
||||||
* @param {string} msg msg
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const attempt = (
|
|
||||||
err: Error | null | undefined,
|
|
||||||
data: unknown,
|
|
||||||
msg: string
|
|
||||||
) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
failed(err, data);
|
failed(err, data);
|
||||||
} else {
|
} else {
|
||||||
if (method === AUTH_METHODS['CRAM-MD5']) {
|
if (method === AUTH_METHODS['CRAM-MD5']) {
|
||||||
this.command(encodeCramMd5(msg), response, [235, 503]);
|
this.command(encodeCramMd5(msg as string), response, [235, 503]);
|
||||||
} else if (method === AUTH_METHODS.LOGIN) {
|
} else if (method === AUTH_METHODS.LOGIN) {
|
||||||
this.command(
|
this.command(
|
||||||
Buffer.from(login.password()).toString('base64'),
|
Buffer.from(login.password()).toString('base64'),
|
||||||
|
@ -830,13 +811,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const attemptUser: SMTPCommandCallback = (err, data) => {
|
||||||
* @param {Error} err err
|
|
||||||
* @param {unknown} data data
|
|
||||||
* @param {string} msg msg
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const attemptUser = (err: Error, data: unknown) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
failed(err, data);
|
failed(err, data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -872,8 +847,7 @@ export class SMTPConnection extends EventEmitter {
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
caller(
|
callback(
|
||||||
callback,
|
|
||||||
SMTPError.create(
|
SMTPError.create(
|
||||||
'no form of authorization supported',
|
'no form of authorization supported',
|
||||||
SMTPErrorStates.AUTHNOTSUPPORTED,
|
SMTPErrorStates.AUTHNOTSUPPORTED,
|
||||||
|
@ -918,14 +892,14 @@ export class SMTPConnection extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {function(...any[]): void} [callback] function to call after response
|
* @param {SMTPCommandCallback} [callback] function to call after response
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public quit(callback?: (...rest: any[]) => void) {
|
public quit(callback?: SMTPCommandCallback) {
|
||||||
this.command(
|
this.command(
|
||||||
'quit',
|
'quit',
|
||||||
(err, data) => {
|
(err, data) => {
|
||||||
caller(callback, err, data);
|
callback?.(err, data);
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
[221, 250]
|
[221, 250]
|
||||||
|
|
|
@ -76,7 +76,7 @@ export interface MessageHeaders {
|
||||||
'return-path'?: string | null;
|
'return-path'?: string | null;
|
||||||
date?: string;
|
date?: string;
|
||||||
from: string | string[];
|
from: string | string[];
|
||||||
to: string | string[];
|
to?: string | string[];
|
||||||
cc?: string | string[];
|
cc?: string | string[];
|
||||||
bcc?: string | string[];
|
bcc?: string | string[];
|
||||||
subject: string;
|
subject: string;
|
||||||
|
@ -137,7 +137,7 @@ export class Message {
|
||||||
* @see https://tools.ietf.org/html/rfc2822
|
* @see https://tools.ietf.org/html/rfc2822
|
||||||
* @param {Partial<MessageHeaders>} headers Message headers
|
* @param {Partial<MessageHeaders>} headers Message headers
|
||||||
*/
|
*/
|
||||||
constructor(headers: Partial<MessageHeaders>) {
|
constructor(headers: Partial<MessageHeaders> = {}) {
|
||||||
for (const header in headers) {
|
for (const header in headers) {
|
||||||
// allow user to override default content-type to override charset or send a single non-text message
|
// allow user to override default content-type to override charset or send a single non-text message
|
||||||
if (/^content-type$/i.test(header)) {
|
if (/^content-type$/i.test(header)) {
|
||||||
|
|
77
test/auth.ts
77
test/auth.ts
|
@ -1,39 +1,23 @@
|
||||||
import test from 'ava';
|
import test from 'ava';
|
||||||
import type { ExecutionContext } from 'ava';
|
|
||||||
import { simpleParser } from 'mailparser';
|
|
||||||
import type { AddressObject } from 'mailparser';
|
|
||||||
import { SMTPServer } from 'smtp-server';
|
import { SMTPServer } from 'smtp-server';
|
||||||
|
|
||||||
import { AUTH_METHODS, SMTPClient, Message } from '../email.js';
|
import { AUTH_METHODS, SMTPConnection } from '../email.js';
|
||||||
|
|
||||||
let port = 2000;
|
let port = 2000;
|
||||||
|
|
||||||
function send(
|
function connect({
|
||||||
t: ExecutionContext,
|
authMethods = [],
|
||||||
{
|
authOptional = false,
|
||||||
authMethods = [],
|
secure = false,
|
||||||
authOptional = false,
|
}: {
|
||||||
secure = false,
|
authMethods?: (keyof typeof AUTH_METHODS)[];
|
||||||
}: {
|
authOptional?: boolean;
|
||||||
authMethods?: (keyof typeof AUTH_METHODS)[];
|
secure?: boolean;
|
||||||
authOptional?: boolean;
|
} = {}) {
|
||||||
secure?: boolean;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
t.plan(5);
|
|
||||||
|
|
||||||
const msg = {
|
|
||||||
subject: 'this is a test TEXT message from emailjs',
|
|
||||||
from: 'piglet@gmail.com',
|
|
||||||
to: 'pooh@gmail.com',
|
|
||||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
|
||||||
};
|
|
||||||
const server = new SMTPServer({
|
const server = new SMTPServer({
|
||||||
authMethods,
|
authMethods,
|
||||||
secure: secure,
|
secure: secure,
|
||||||
hideSTARTTLS: !secure,
|
|
||||||
authOptional,
|
|
||||||
onAuth(auth, _session, callback) {
|
onAuth(auth, _session, callback) {
|
||||||
const { accessToken, method, username, password } = auth;
|
const { accessToken, method, username, password } = auth;
|
||||||
if (
|
if (
|
||||||
|
@ -49,28 +33,19 @@ function send(
|
||||||
return callback(new Error('invalid user / pass'));
|
return callback(new Error('invalid user / pass'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onData(stream, _session, callback: () => void) {
|
|
||||||
const mail = await simpleParser(stream, {
|
|
||||||
skipHtmlToText: true,
|
|
||||||
skipTextToHtml: true,
|
|
||||||
skipImageLinks: true,
|
|
||||||
} as Record<string, unknown>);
|
|
||||||
|
|
||||||
t.is(mail.text, msg.text + '\n\n\n');
|
|
||||||
t.is(mail.subject, msg.subject);
|
|
||||||
t.is(mail.from?.text, msg.from);
|
|
||||||
t.is((mail.to as AddressObject).text, msg.to);
|
|
||||||
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const p = port++;
|
const p = port++;
|
||||||
server.listen(p, () => {
|
server.listen(p, () => {
|
||||||
const options = Object.assign(
|
const options = Object.assign(
|
||||||
{ port: p, ssl: secure, authentication: authMethods },
|
{
|
||||||
authOptional ? {} : { user: 'pooh', password: 'honey' }
|
port: p,
|
||||||
|
authentication: authMethods,
|
||||||
|
},
|
||||||
|
authOptional
|
||||||
|
? { ssl: secure }
|
||||||
|
: { ssl: secure, user: 'pooh', password: 'honey' }
|
||||||
);
|
);
|
||||||
new SMTPClient(options).send(new Message(msg), (err) => {
|
new SMTPConnection(options).connect((err) => {
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err.message);
|
reject(err.message);
|
||||||
|
@ -84,39 +59,39 @@ function send(
|
||||||
}
|
}
|
||||||
|
|
||||||
test('no authentication (unencrypted) should succeed', async (t) => {
|
test('no authentication (unencrypted) should succeed', async (t) => {
|
||||||
await t.notThrowsAsync(send(t, { authOptional: true }));
|
await t.notThrowsAsync(connect({ authOptional: true }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('no authentication (encrypted) should succeed', async (t) => {
|
test('no authentication (encrypted) should succeed', async (t) => {
|
||||||
await t.notThrowsAsync(send(t, { authOptional: true, secure: true }));
|
await t.notThrowsAsync(connect({ authOptional: true, secure: true }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PLAIN authentication (unencrypted) should succeed', async (t) => {
|
test('PLAIN authentication (unencrypted) should succeed', async (t) => {
|
||||||
await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.PLAIN] }));
|
await t.notThrowsAsync(connect({ authMethods: [AUTH_METHODS.PLAIN] }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PLAIN authentication (encrypted) should succeed', async (t) => {
|
test('PLAIN authentication (encrypted) should succeed', async (t) => {
|
||||||
await t.notThrowsAsync(
|
await t.notThrowsAsync(
|
||||||
send(t, { authMethods: [AUTH_METHODS.PLAIN], secure: true })
|
connect({ authMethods: [AUTH_METHODS.PLAIN], secure: true })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('LOGIN authentication (unencrypted) should succeed', async (t) => {
|
test('LOGIN authentication (unencrypted) should succeed', async (t) => {
|
||||||
await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.LOGIN] }));
|
await t.notThrowsAsync(connect({ authMethods: [AUTH_METHODS.LOGIN] }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('LOGIN authentication (encrypted) should succeed', async (t) => {
|
test('LOGIN authentication (encrypted) should succeed', async (t) => {
|
||||||
await t.notThrowsAsync(
|
await t.notThrowsAsync(
|
||||||
send(t, { authMethods: [AUTH_METHODS.LOGIN], secure: true })
|
connect({ authMethods: [AUTH_METHODS.LOGIN], secure: true })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('XOAUTH2 authentication (unencrypted) should succeed', async (t) => {
|
test('XOAUTH2 authentication (unencrypted) should succeed', async (t) => {
|
||||||
await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.XOAUTH2] }));
|
await t.notThrowsAsync(connect({ authMethods: [AUTH_METHODS.XOAUTH2] }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('XOAUTH2 authentication (encrypted) should succeed', async (t) => {
|
test('XOAUTH2 authentication (encrypted) should succeed', async (t) => {
|
||||||
await t.notThrowsAsync(
|
await t.notThrowsAsync(
|
||||||
send(t, { authMethods: [AUTH_METHODS.XOAUTH2], secure: true })
|
connect({ authMethods: [AUTH_METHODS.XOAUTH2], secure: true })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
|
||||||
import test from 'ava';
|
import test from 'ava';
|
||||||
import { simpleParser } from 'mailparser';
|
|
||||||
import type { ParsedMail, AddressObject } from 'mailparser';
|
|
||||||
import { SMTPServer } from 'smtp-server';
|
import { SMTPServer } from 'smtp-server';
|
||||||
|
|
||||||
import type { MessageHeaders } from '../email.js';
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_TIMEOUT,
|
DEFAULT_TIMEOUT,
|
||||||
SMTPClient,
|
SMTPClient,
|
||||||
|
@ -13,7 +10,6 @@ import {
|
||||||
isRFC2822Date,
|
isRFC2822Date,
|
||||||
} from '../email.js';
|
} from '../email.js';
|
||||||
|
|
||||||
const parseMap = new Map<string, ParsedMail>();
|
|
||||||
const port = 3333;
|
const port = 3333;
|
||||||
let greylistPort = 4444;
|
let greylistPort = 4444;
|
||||||
|
|
||||||
|
@ -32,30 +28,8 @@ const server = new SMTPServer({
|
||||||
return callback(new Error('invalid user / pass'));
|
return callback(new Error('invalid user / pass'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onData(stream, _session, callback: () => void) {
|
|
||||||
const mail = await simpleParser(stream, {
|
|
||||||
skipHtmlToText: true,
|
|
||||||
skipTextToHtml: true,
|
|
||||||
skipImageLinks: true,
|
|
||||||
} as Record<string, unknown>);
|
|
||||||
|
|
||||||
parseMap.set(mail.subject as string, mail);
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function send(headers: Partial<MessageHeaders>) {
|
|
||||||
return new Promise<ParsedMail>((resolve, reject) => {
|
|
||||||
client.send(new Message(headers), (err) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(parseMap.get(headers.subject as string) as ParsedMail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
test.before(async (t) => {
|
test.before(async (t) => {
|
||||||
server.listen(port, t.pass);
|
server.listen(port, t.pass);
|
||||||
});
|
});
|
||||||
|
@ -165,17 +139,17 @@ test('client accepts array sender', async (t) => {
|
||||||
|
|
||||||
test('client rejects message without `from` header', async (t) => {
|
test('client rejects message without `from` header', async (t) => {
|
||||||
const error = await t.throwsAsync(
|
const error = await t.throwsAsync(
|
||||||
send({
|
client.sendAsync({
|
||||||
subject: 'this is a test TEXT message from emailjs',
|
subject: 'this is a test TEXT message from emailjs',
|
||||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||||
})
|
} as never)
|
||||||
);
|
);
|
||||||
t.is(error?.message, 'Message must have a `from` header');
|
t.is(error?.message, 'Message must have a `from` header');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('client rejects message without `to`, `cc`, or `bcc` header', async (t) => {
|
test('client rejects message without `to`, `cc`, or `bcc` header', async (t) => {
|
||||||
const error = await t.throwsAsync(
|
const error = await t.throwsAsync(
|
||||||
send({
|
client.sendAsync({
|
||||||
subject: 'this is a test TEXT message from emailjs',
|
subject: 'this is a test TEXT message from emailjs',
|
||||||
from: 'piglet@gmail.com',
|
from: 'piglet@gmail.com',
|
||||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||||
|
@ -195,11 +169,12 @@ test('client allows message with only `cc` recipient header', async (t) => {
|
||||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||||
};
|
};
|
||||||
|
|
||||||
const mail = await send(msg);
|
const message = await client.sendAsync(msg);
|
||||||
t.is(mail.text, msg.text + '\n\n\n');
|
t.is(message.text, msg.text);
|
||||||
t.is(mail.subject, msg.subject);
|
t.is(message.header.from, msg.from);
|
||||||
t.is(mail.from?.text, msg.from);
|
t.is(message.header.to, undefined);
|
||||||
t.is((mail.cc as AddressObject).text, msg.cc);
|
t.is(message.header.cc, msg.cc);
|
||||||
|
t.is(message.header.bcc, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('client allows message with only `bcc` recipient header', async (t) => {
|
test('client allows message with only `bcc` recipient header', async (t) => {
|
||||||
|
@ -210,11 +185,12 @@ test('client allows message with only `bcc` recipient header', async (t) => {
|
||||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||||
};
|
};
|
||||||
|
|
||||||
const mail = await send(msg);
|
const message = await client.sendAsync(msg);
|
||||||
t.is(mail.text, msg.text + '\n\n\n');
|
t.is(message.text, msg.text);
|
||||||
t.is(mail.subject, msg.subject);
|
t.is(message.header.from, msg.from);
|
||||||
t.is(mail.from?.text, msg.from);
|
t.is(message.header.to, undefined);
|
||||||
t.is(mail.bcc, undefined);
|
t.is(message.header.cc, undefined);
|
||||||
|
t.is(message.header.bcc, msg.bcc);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('client constructor throws if `password` supplied without `user`', async (t) => {
|
test('client constructor throws if `password` supplied without `user`', async (t) => {
|
||||||
|
|
|
@ -1,11 +1,51 @@
|
||||||
import test from 'ava';
|
import test from 'ava';
|
||||||
|
import { SMTPServer } from 'smtp-server';
|
||||||
|
|
||||||
import { SMTPConnection } from '../email.js';
|
import { SMTPConnection } from '../email.js';
|
||||||
|
import type { SMTPConnectionOptions } from '../email.js';
|
||||||
|
|
||||||
|
const port = 6666;
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
function testConnection(options: Partial<SMTPConnectionOptions> = {}) {
|
||||||
|
const increment = counter++;
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const { ssl } = options;
|
||||||
|
const server = new SMTPServer(ssl ? { secure: true } : undefined);
|
||||||
|
server.listen(port + increment, () => {
|
||||||
|
const connection = new SMTPConnection(options);
|
||||||
|
connection.connect((err) => {
|
||||||
|
server.close();
|
||||||
|
connection.close(true);
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, port + increment);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test('accepts a custom logger', async (t) => {
|
test('accepts a custom logger', async (t) => {
|
||||||
const logger = () => {
|
const logger = () => {
|
||||||
/** ø */
|
/** ø */
|
||||||
};
|
};
|
||||||
const connection = new SMTPConnection({ logger });
|
t.is(Reflect.get(new SMTPConnection({ logger }), 'log'), logger);
|
||||||
t.is(Reflect.get(connection, 'log'), logger);
|
});
|
||||||
|
|
||||||
|
test('can connect without encryption', async (t) => {
|
||||||
|
return await t.notThrowsAsync(testConnection());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can connect with ssl', async (t) => {
|
||||||
|
return await t.notThrowsAsync(testConnection({ ssl: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can connect with tls', async (t) => {
|
||||||
|
return await t.notThrowsAsync(testConnection({ tls: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can connect with tls and ssl', async (t) => {
|
||||||
|
return await t.notThrowsAsync(testConnection({ ssl: true, tls: true }));
|
||||||
});
|
});
|
||||||
|
|
105
yarn.lock
105
yarn.lock
|
@ -706,10 +706,10 @@ emoji-regex@^9.2.2:
|
||||||
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||||
|
|
||||||
encoding-japanese@1.0.30:
|
encoding-japanese@2.0.0:
|
||||||
version "1.0.30"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-1.0.30.tgz#537c4d62881767925d601acb4c79fb14db81703a"
|
resolved "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz#fa0226e5469e7b5b69a04fea7d5481bd1fa56936"
|
||||||
integrity sha512-bd/DFLAoJetvv7ar/KIpE3CNO8wEuyrt9Xuw6nSMiZ+Vrz/Q21BPsMHvARL2Wz6IKHKXgb+DWZqtRg1vql9cBg==
|
integrity sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==
|
||||||
|
|
||||||
entities@^2.0.0:
|
entities@^2.0.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
|
@ -1054,16 +1054,16 @@ he@1.2.0, he@^1.2.0:
|
||||||
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
html-to-text@8.0.0:
|
html-to-text@8.2.0:
|
||||||
version "8.0.0"
|
version "8.2.0"
|
||||||
resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-8.0.0.tgz#5848681a5a38d657a7bb58cf5006d1c29fe64ce3"
|
resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-8.2.0.tgz#8b35e280ba7fc27710b7aa76d4500aab30731924"
|
||||||
integrity sha512-fEtul1OerF2aMEV+Wpy+Ue20tug134jOY1GIudtdqZi7D0uTudB2tVJBKfVhTL03dtqeJoF8gk8EPX9SyMEvLg==
|
integrity sha512-CLXExYn1b++Lgri+ZyVvbUEFwzkLZppjjZOwB7X1qv2jIi8MrMEvxWX5KQ7zATAzTvcqgmtO00M2kCRMtEdOKQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@selderee/plugin-htmlparser2" "^0.6.0"
|
"@selderee/plugin-htmlparser2" "^0.6.0"
|
||||||
deepmerge "^4.2.2"
|
deepmerge "^4.2.2"
|
||||||
he "^1.2.0"
|
he "^1.2.0"
|
||||||
htmlparser2 "^6.1.0"
|
htmlparser2 "^6.1.0"
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.6"
|
||||||
selderee "^0.6.0"
|
selderee "^0.6.0"
|
||||||
|
|
||||||
htmlparser2@^6.1.0:
|
htmlparser2@^6.1.0:
|
||||||
|
@ -1076,13 +1076,6 @@ htmlparser2@^6.1.0:
|
||||||
domutils "^2.5.2"
|
domutils "^2.5.2"
|
||||||
entities "^2.0.0"
|
entities "^2.0.0"
|
||||||
|
|
||||||
iconv-lite@0.6.2:
|
|
||||||
version "0.6.2"
|
|
||||||
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
|
|
||||||
integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
|
|
||||||
dependencies:
|
|
||||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
|
||||||
|
|
||||||
iconv-lite@0.6.3, iconv-lite@^0.6.3:
|
iconv-lite@0.6.3, iconv-lite@^0.6.3:
|
||||||
version "0.6.3"
|
version "0.6.3"
|
||||||
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||||
|
@ -1265,13 +1258,13 @@ libbase64@1.2.1:
|
||||||
resolved "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz#fb93bf4cb6d730f29b92155b6408d1bd2176a8c8"
|
resolved "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz#fb93bf4cb6d730f29b92155b6408d1bd2176a8c8"
|
||||||
integrity sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==
|
integrity sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==
|
||||||
|
|
||||||
libmime@5.0.0:
|
libmime@5.1.0:
|
||||||
version "5.0.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.npmjs.org/libmime/-/libmime-5.0.0.tgz#4759c76eb219985c5d4057b3a9359922194d9ff7"
|
resolved "https://registry.npmjs.org/libmime/-/libmime-5.1.0.tgz#d9a1c4a85c982fa4e64c2c841f95e3827c3f71d2"
|
||||||
integrity sha512-2Bm96d5ktnE217Ib1FldvUaPAaOst6GtZrsxJCwnJgi9lnsoAKIHyU0sae8rNx6DNYbjdqqh8lv5/b9poD8qOg==
|
integrity sha512-xOqorG21Va+3CjpFOfFTU7SWohHH2uIX9ZY4Byz6J+lvpfvc486tOAT/G9GfbrKtJ9O7NCX9o0aC2lxqbnZ9EA==
|
||||||
dependencies:
|
dependencies:
|
||||||
encoding-japanese "1.0.30"
|
encoding-japanese "2.0.0"
|
||||||
iconv-lite "0.6.2"
|
iconv-lite "0.6.3"
|
||||||
libbase64 "1.2.1"
|
libbase64 "1.2.1"
|
||||||
libqp "1.1.0"
|
libqp "1.1.0"
|
||||||
|
|
||||||
|
@ -1280,10 +1273,10 @@ libqp@1.1.0:
|
||||||
resolved "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz#f5e6e06ad74b794fb5b5b66988bf728ef1dedbe8"
|
resolved "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz#f5e6e06ad74b794fb5b5b66988bf728ef1dedbe8"
|
||||||
integrity sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=
|
integrity sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=
|
||||||
|
|
||||||
linkify-it@3.0.3:
|
linkify-it@4.0.0:
|
||||||
version "3.0.3"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
|
resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.0.tgz#4f2d16879adc637cdfe9056cbc02de30e88ffa32"
|
||||||
integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==
|
integrity sha512-QAxkXyzT/TXgwGyY4rTgC95Ex6/lZ5/lYTV9nug6eJt93BCBQGOE47D/g2+/m5J1MrVLr2ot97OXkBZ9bBpR4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
uc.micro "^1.0.1"
|
uc.micro "^1.0.1"
|
||||||
|
|
||||||
|
@ -1316,28 +1309,28 @@ lru-cache@^6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist "^4.0.0"
|
yallist "^4.0.0"
|
||||||
|
|
||||||
mailparser@3.4.0:
|
mailparser@3.5.0:
|
||||||
version "3.4.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.npmjs.org/mailparser/-/mailparser-3.4.0.tgz#249869bc5a41af9e0eabbf005197789442fbac9e"
|
resolved "https://registry.npmjs.org/mailparser/-/mailparser-3.5.0.tgz#5b333b0ef2f063a7db9d24ed95f29efb464cbef3"
|
||||||
integrity sha512-u2pfpLg+xr7m2FKDl+ohQhy2gMok1QZ+S9E5umS9ez5DSJWttrqSmBGswyj9F68pZMVTwbhLpBt7Kd04q/W4Vw==
|
integrity sha512-mdr2DFgz8LKC0/Q6io6znA0HVnzaPFT0a4TTnLeZ7mWHlkfnm227Wxlq7mHh7AgeP32h7gOUpXvyhSfJJIEeyg==
|
||||||
dependencies:
|
dependencies:
|
||||||
encoding-japanese "1.0.30"
|
encoding-japanese "2.0.0"
|
||||||
he "1.2.0"
|
he "1.2.0"
|
||||||
html-to-text "8.0.0"
|
html-to-text "8.2.0"
|
||||||
iconv-lite "0.6.3"
|
iconv-lite "0.6.3"
|
||||||
libmime "5.0.0"
|
libmime "5.1.0"
|
||||||
linkify-it "3.0.3"
|
linkify-it "4.0.0"
|
||||||
mailsplit "5.3.1"
|
mailsplit "5.3.2"
|
||||||
nodemailer "6.7.0"
|
nodemailer "6.7.3"
|
||||||
tlds "1.224.0"
|
tlds "1.231.0"
|
||||||
|
|
||||||
mailsplit@5.3.1:
|
mailsplit@5.3.2:
|
||||||
version "5.3.1"
|
version "5.3.2"
|
||||||
resolved "https://registry.npmjs.org/mailsplit/-/mailsplit-5.3.1.tgz#dd6d5c20a7b8a767fe9c9649dfcb26ee04f84c36"
|
resolved "https://registry.npmjs.org/mailsplit/-/mailsplit-5.3.2.tgz#c344c019f631be4f54d5213509637127e3e3dd66"
|
||||||
integrity sha512-o6R6HCzqWYmI2/IYlB+v2IMPgYqC2EynmagZQICAhR7zAq0CO6fPcsO6CrYmVuYT+SSwvLAEZR5WniohBELcAA==
|
integrity sha512-coES12hhKqagkuBTJoqERX+y9bXNpxbxw3Esd07auuwKYmcagouVlgucyIVRp48fnswMKxcUtLoFn/L1a75ynQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
libbase64 "1.2.1"
|
libbase64 "1.2.1"
|
||||||
libmime "5.0.0"
|
libmime "5.1.0"
|
||||||
libqp "1.1.0"
|
libqp "1.1.0"
|
||||||
|
|
||||||
make-error@^1.1.1:
|
make-error@^1.1.1:
|
||||||
|
@ -1399,7 +1392,7 @@ minimatch@^3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@^1.2.5:
|
minimist@^1.2.6:
|
||||||
version "1.2.6"
|
version "1.2.6"
|
||||||
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
|
@ -1434,10 +1427,10 @@ nearley@^2.20.1:
|
||||||
railroad-diagrams "^1.0.0"
|
railroad-diagrams "^1.0.0"
|
||||||
randexp "0.4.6"
|
randexp "0.4.6"
|
||||||
|
|
||||||
nodemailer@6.7.0, nodemailer@6.7.2, nodemailer@6.7.3:
|
nodemailer@6.7.3, nodemailer@6.7.4:
|
||||||
version "6.7.3"
|
version "6.7.4"
|
||||||
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.3.tgz#b73f9a81b9c8fa8acb4ea14b608f5e725ea8e018"
|
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.4.tgz#28771bda3dda8f2dad1912aca0f8727ce7f09d89"
|
||||||
integrity sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g==
|
integrity sha512-TBSS3qS8WG45ycUwEvEA/3UM1o3sLz9jUl4TPUKPz4ImWWM6UgRCb5pLO+HOouDKEj57yNLOrzQlO8+9IjWZoA==
|
||||||
|
|
||||||
nofilter@^3.1.0:
|
nofilter@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
|
@ -1760,14 +1753,14 @@ slice-ansi@^5.0.0:
|
||||||
ansi-styles "^6.0.0"
|
ansi-styles "^6.0.0"
|
||||||
is-fullwidth-code-point "^4.0.0"
|
is-fullwidth-code-point "^4.0.0"
|
||||||
|
|
||||||
smtp-server@3.10.0:
|
smtp-server@3.11.0:
|
||||||
version "3.10.0"
|
version "3.11.0"
|
||||||
resolved "https://registry.npmjs.org/smtp-server/-/smtp-server-3.10.0.tgz#8801fa7983558a58f369c03b9a271b35131abb86"
|
resolved "https://registry.npmjs.org/smtp-server/-/smtp-server-3.11.0.tgz#8820c191124fab37a8f16c8325a7f1fd38092c4f"
|
||||||
integrity sha512-X2lpQhryNtkSYAGgWPnVA+3aU4NCmaVXToYiaYv5JGpDy7zA6QmoP46r1OdVlgt6KTKXvEsYFvviHGs0VYSphg==
|
integrity sha512-j/W6mEKeMNKuiM9oCAAjm87agPEN1O3IU4cFLT4ZOCyyq3UXN7HiIXF+q7izxJcYSar15B/JaSxcijoPCR8Tag==
|
||||||
dependencies:
|
dependencies:
|
||||||
base32.js "0.1.0"
|
base32.js "0.1.0"
|
||||||
ipv6-normalize "1.0.1"
|
ipv6-normalize "1.0.1"
|
||||||
nodemailer "6.7.2"
|
nodemailer "6.7.3"
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
|
@ -1855,10 +1848,10 @@ time-zone@^1.0.0:
|
||||||
resolved "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz#99c5bf55958966af6d06d83bdf3800dc82faec5d"
|
resolved "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz#99c5bf55958966af6d06d83bdf3800dc82faec5d"
|
||||||
integrity sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=
|
integrity sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=
|
||||||
|
|
||||||
tlds@1.224.0:
|
tlds@1.231.0:
|
||||||
version "1.224.0"
|
version "1.231.0"
|
||||||
resolved "https://registry.npmjs.org/tlds/-/tlds-1.224.0.tgz#dc9a5b0bda0708af0302114f6e24458770c5af01"
|
resolved "https://registry.npmjs.org/tlds/-/tlds-1.231.0.tgz#93880175cd0a06fdf7b5b5b9bcadff9d94813e39"
|
||||||
integrity sha512-Jgdc8SEijbDFUsmCn6Wk/f7E6jBLFZOG3U1xK0amGSfEH55Xx97ItUS/d2NngsuApjn11UeWCWj8Um3VRhseZQ==
|
integrity sha512-L7UQwueHSkGxZHQBXHVmXW64oi+uqNtzFt2x6Ssk7NVnpIbw16CRs4eb/jmKOZ9t2JnqZ/b3Cfvo97lnXqKrhw==
|
||||||
|
|
||||||
to-regex-range@^5.0.1:
|
to-regex-range@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
|
|
Loading…
Reference in New Issue