Skip to content

Commit

Permalink
Revert "Revert "feat: export SMTP transport options of nodemailer"" (
Browse files Browse the repository at this point in the history
  • Loading branch information
jjtang1985 authored Sep 20, 2022
1 parent 2627bcf commit d1bf2de
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/chips-and-fish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sap-cloud-sdk/mail-client': minor
---

[New Functionality] Expose SMTP transport options of `nodemailer`.
4 changes: 3 additions & 1 deletion packages/mail-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ export type {
Envelope,
MailResponse,
MailDestination,
MailClientOptions
MailClientOptions,
SmtpTransportOptions,
SDKOptions
} from './mail-client-types';
71 changes: 69 additions & 2 deletions packages/mail-client/src/mail-client-types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Readable } from 'stream';
import { Url } from 'url';
import { ConnectionOptions } from 'tls';
import net from 'net';
import {
AuthenticationType,
DestinationProxyType,
ProxyConfiguration
} from '@sap-cloud-sdk/connectivity';

/**
* Represents an e-mail address.
* This interface is compatible with `Mail.Address` of `nodemailer`.
Expand Down Expand Up @@ -259,11 +262,75 @@ export interface MailDestination {
}

/**
* Represents options of the mail client.
* Represents options for sending mails provided by the SDK. For example whether the mails are sent in parallel.
*/
export interface MailClientOptions {
export interface SDKOptions {
/**
* Option to define the strategy of sending emails. The emails will be sent in parallel when setting to true, otherwise in sequential. The default value is true.
*/
parallel?: boolean;
}

/**
* Represents options of the mail client.
*/
export interface MailClientOptions extends SmtpTransportOptions {
/**
* Defines the SDK behaviours, for example whether the mails are sent in parallel.
*/
sdkOptions?: SDKOptions;
}

/**
* Represents options for setting up the SMTP connection.
* This interface is compatible with `SMTPConnection.Options` of `nodemailer`.
* @experimental This API is experimental and might change in newer versions. Use with caution.
*/
export interface SmtpTransportOptions {
/**
* Defines if the connection should use SSL (if true) or not (if false).
*/
secure?: 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;
/**
* How many milliseconds to wait for the connection to establish.
*/
connectionTimeout?: number | undefined;
/**
* How many milliseconds to wait for the greeting after connection is established.
*/
greetingTimeout?: number | undefined;
/**
* How many milliseconds of inactivity to allow.
*/
socketTimeout?: number | undefined;
/**
* If set to true, then logs SMTP traffic and message content, otherwise logs only transaction events.
*/
debug?: boolean | undefined;
/**
* Defines additional options to be passed to the socket constructor.
* @example
* { rejectUnauthorized: true }
*/
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;
}
44 changes: 41 additions & 3 deletions packages/mail-client/src/mail-client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import nodemailer from 'nodemailer';
import { SocksClient } from 'socks';
import { Protocol } from '@sap-cloud-sdk/connectivity';
import { buildSocksProxy, sendMail } from './mail-client';
import { MailDestination, MailConfig } from './mail-client-types';
import {
buildSocksProxy,
isMailSentInSequential,
sendMail
} from './mail-client';
import {
MailDestination,
MailConfig,
MailClientOptions
} from './mail-client-types';

describe('mail client', () => {
beforeEach(() => {
Expand Down Expand Up @@ -94,7 +102,7 @@ describe('mail client', () => {
to: '[email protected]'
};
await expect(
sendMail(destination, mailOptions, { parallel: false })
sendMail(destination, mailOptions, { sdkOptions: { parallel: false } })
).resolves.not.toThrow();
expect(spyCreateSocket).toBeCalledTimes(1);
expect(spyCreateTransport).toBeCalledTimes(1);
Expand All @@ -106,6 +114,36 @@ describe('mail client', () => {
});
});

describe('isMailSentInSequential', () => {
it('should return false when the mail client options is undefined', () => {
expect(isMailSentInSequential()).toBe(false);
});

it('should return false when the sdk options is undefined', () => {
const mailClientOptions: MailClientOptions = {};
expect(isMailSentInSequential(mailClientOptions)).toBe(false);
});

it('should return false when the parallel option is undefined', () => {
const mailClientOptions: MailClientOptions = { sdkOptions: {} };
expect(isMailSentInSequential(mailClientOptions)).toBe(false);
});

it('should return false when the parallel option is set to true', () => {
const mailClientOptions: MailClientOptions = {
sdkOptions: { parallel: true }
};
expect(isMailSentInSequential(mailClientOptions)).toBe(false);
});

it('should return true when the parallel option is set to false', () => {
const mailClientOptions: MailClientOptions = {
sdkOptions: { parallel: false }
};
expect(isMailSentInSequential(mailClientOptions)).toBe(true);
});
});

describe('buildSocksProxy', () => {
it('build valid socks proxy', () => {
const dest: MailDestination = {
Expand Down
47 changes: 21 additions & 26 deletions packages/mail-client/src/mail-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,46 +133,28 @@ async function createSocket(mailDestination: MailDestination): Promise<Socket> {

async function createTransport(
mailDestination: MailDestination,
mailClientOptions?: MailClientOptions,
socket?: Socket
): Promise<Transporter<SentMessageInfo>> {
const baseOptions: Options = {
pool: true,
// TODO: expose an option, so the user can decide
// Defines if the connection should use SSL (if true) or not (if false). See: https://nodemailer.com/smtp/
// true for 465, false for other ports
// secure: false,
auth: {
user: mailDestination.username,
pass: mailDestination.password
},
// TODO: Uploading certificates on CF like HTTP destination is not applicable for MAIL destination.
// Provide an API option for the users, so they can pass it as parameter for the nodemailer.
// The `tls` is the right key:
// tls: {
// cert: xxx,
// ca: xxx,
// rejectUnauthorized: xxx
// }
tls: {
/**
* If true the server will reject any connection which is not
* authorized with the list of supplied CAs. This option only has an
* effect if requestCert is true.
*/
/** Disable tls config to fix the self signed certificate error. */
rejectUnauthorized: false
}
};
if (socket) {
return nodemailer.createTransport({
...baseOptions,
connection: socket
connection: socket,
...mailClientOptions
});
}
return nodemailer.createTransport({
...baseOptions,
host: mailDestination.host,
port: mailDestination.port
port: mailDestination.port,
...mailClientOptions
});
}

Expand Down Expand Up @@ -244,12 +226,16 @@ async function sendMailWithNodemailer<T extends MailConfig>(
if (mailDestination.proxyType === 'OnPremise') {
socket = await createSocket(mailDestination);
}
const transport = await createTransport(mailDestination, socket);
const transport = await createTransport(
mailDestination,
mailClientOptions,
socket
);
const mailConfigsFromDestination =
buildMailConfigsFromDestination(mailDestination);

let response: MailResponse[];
if (mailClientOptions && mailClientOptions.parallel === false) {
if (isMailSentInSequential(mailClientOptions)) {
response = await sendMailInSequential(
transport,
mailConfigsFromDestination,
Expand Down Expand Up @@ -283,7 +269,7 @@ function teardown(transport: Transporter<SentMessageInfo>, socket?: Socket) {
* This function also does the destination look up, when passing `DestinationOrFetchOptions`.
* @param destination - A destination or a destination name and a JWT.
* @param mailConfigs - A single object or an array of {@link MailConfig}.
* @param mailClientOptions - A {@link MailClientOptions} that defines the configurations of the mail client, e.g., whether the mails are sent in parallel.
* @param mailClientOptions - A {@link MailClientOptions} that defines the configurations of the mail client, e.g., how to set up an SMTP transport, including SSL and tls configurations.
* @returns A promise resolving to an array of {@link MailResponse}.
* @see https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destination#referencing-destinations-by-name
*/
Expand Down Expand Up @@ -315,3 +301,12 @@ function transformToArray<T>(singleElementOrArray: T | T[]): T[] {
? singleElementOrArray
: [singleElementOrArray];
}

/**
* @internal
*/
export function isMailSentInSequential(
mailClientOptions?: MailClientOptions
): boolean {
return mailClientOptions?.sdkOptions?.parallel === false;
}
6 changes: 5 additions & 1 deletion test-packages/e2e-tests/test/mail/mail.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ async function sendTestMail(
type: 'MAIL',
originalProperties
};
return sendMail(destination, mainConfigs);
return sendMail(destination, mainConfigs, {
tls: {
rejectUnauthorized: false
}
});
}

function buildArrayWithNatualNums(length): number[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ expectType<Promise<MailResponse[]>>(

expectType<Promise<MailResponse[]>>(
sendMail({ destinationName: 'dest' }, [mailConfig, mailConfig], {
parallel: true
tls: {
rejectUnauthorized: false
}
})
);

expectType<Promise<MailResponse[]>>(
sendMail({ destinationName: 'dest' }, [mailConfig, mailConfig], {
parallel: false
sdkOptions: {
parallel: false
}
})
);

0 comments on commit d1bf2de

Please sign in to comment.