From de5f1938f6e3f15aeb1fcbbee05970f432a985be Mon Sep 17 00:00:00 2001 From: Antoine SEIN <142824551+asein-sinch@users.noreply.github.com> Date: Wed, 13 Nov 2024 21:55:10 +0100 Subject: [PATCH] DEVEXP-642: Implement Mailgun Messages endpoints --- .github/workflows/run-ci.yaml | 2 + examples/simple-examples/package.json | 9 +- examples/simple-examples/src/config.ts | 26 +++ .../src/mailgun/emails/getEmail.ts | 27 +++ .../mailgun/emails/getSendingQueuesStatus.ts | 21 ++ .../src/mailgun/emails/purgeDomainQueues.ts | 21 ++ .../src/mailgun/emails/sendEmail.ts | 37 +++ .../src/mailgun/emails/sendMimeEmail.ts | 47 ++++ jest.config.ts | 5 + packages/mailgun/CHANGELOG.md | 3 + packages/mailgun/README.md | 113 +++++++++ packages/mailgun/cucumber.js | 8 + packages/mailgun/package.json | 38 ++++ packages/mailgun/src/index.ts | 3 + packages/mailgun/src/models/index.ts | 1 + .../request/override-properties/index.ts | 1 + .../override-properties.ts | 34 +++ .../request/send-email-request-base/index.ts | 1 + .../send-email-request-base.ts | 23 ++ .../request/send-email-request/index.ts | 1 + .../send-email-request/send-email-request.ts | 166 ++++++++++++++ .../request/send-mime-email-request/index.ts | 1 + .../send-mime-email-request.ts | 113 +++++++++ .../request/template-properties/index.ts | 1 + .../template-properties.ts | 8 + .../response/bad-request/bad-request.ts | 13 ++ .../v1/emails/response/bad-request/index.ts | 1 + .../email-not-found/email-not-found.ts | 14 ++ .../emails/response/email-not-found/index.ts | 1 + .../exceeded-queue-quota.ts | 22 ++ .../response/exceeded-queue-quota/index.ts | 1 + .../generic-response/generic-response.ts | 14 ++ .../emails/response/generic-response/index.ts | 1 + .../get-email-response/get-email-response.ts | 58 +++++ .../response/get-email-response/index.ts | 1 + .../queue-status-disabled-details/index.ts | 1 + .../queue-status-disabled-details.ts | 4 + .../response/send-email-response/index.ts | 1 + .../send-email-response.ts | 15 ++ .../sending-queues-status-response/index.ts | 1 + .../sending-queues-status-response.ts | 25 ++ packages/mailgun/src/models/v1/index.ts | 13 ++ packages/mailgun/src/rest/index.ts | 1 + .../rest/v1/emails/emails-api.jest.fixture.ts | 32 +++ .../mailgun/src/rest/v1/emails/emails-api.ts | 214 ++++++++++++++++++ packages/mailgun/src/rest/v1/emails/index.ts | 2 + packages/mailgun/src/rest/v1/index.ts | 2 + .../mailgun/src/rest/v1/mailgun-domain-api.ts | 97 ++++++++ .../mailgun/src/rest/v1/mailgun-service.ts | 26 +++ .../tests/models/v1/emails/request/index.ts | 2 + .../request/send-email-request.models.ts | 76 +++++++ .../emails/request/send-email-request.test.ts | 153 +++++++++++++ .../request/send-mime-email-request.models.ts | 34 +++ .../request/send-mime-email-request.test.ts | 63 ++++++ .../v1/emails/response/bad-request.models.ts | 9 + .../v1/emails/response/bad-request.test.ts | 13 ++ .../emails/response/email-not-found.models.ts | 9 + .../emails/response/email-not-found.test.ts | 13 ++ .../response/generic-response.models.ts | 9 + .../emails/response/generic-response.test.ts | 13 ++ .../response/get-email-response.models.ts | 49 ++++ .../response/get-email-response.test.ts | 13 ++ .../tests/models/v1/emails/response/index.ts | 6 + .../response/send-email-response.models.ts | 11 + .../response/send-email-response.test.ts | 15 ++ .../sending-queues-status-response.models.ts | 35 +++ .../sending-queues-status-response.test.ts | 17 ++ .../tests/rest/v1/emails/emails-api.test.ts | 109 +++++++++ .../tests/rest/v1/emails/emails.steps.ts | 112 +++++++++ .../tests/rest/v1/mailgun-domain-api.test.ts | 85 +++++++ .../tests/rest/v1/mailgun-service.test.ts | 44 ++++ packages/mailgun/tsconfig.build.json | 17 ++ packages/mailgun/tsconfig.json | 11 + packages/mailgun/tsconfig.tests.json | 17 ++ .../src/client/api-client-helpers.ts | 10 +- .../sdk-client/src/domain/domain-helper.ts | 5 + .../tests/client/api-client-helpers.test.ts | 5 + packages/sdk-core/package.json | 3 +- packages/sdk-core/src/index.ts | 1 + packages/sdk-core/src/sinch-client.ts | 3 + yarn.lock | 12 + 81 files changed, 2241 insertions(+), 3 deletions(-) create mode 100644 examples/simple-examples/src/mailgun/emails/getEmail.ts create mode 100644 examples/simple-examples/src/mailgun/emails/getSendingQueuesStatus.ts create mode 100644 examples/simple-examples/src/mailgun/emails/purgeDomainQueues.ts create mode 100644 examples/simple-examples/src/mailgun/emails/sendEmail.ts create mode 100644 examples/simple-examples/src/mailgun/emails/sendMimeEmail.ts create mode 100644 packages/mailgun/CHANGELOG.md create mode 100644 packages/mailgun/README.md create mode 100644 packages/mailgun/cucumber.js create mode 100644 packages/mailgun/package.json create mode 100644 packages/mailgun/src/index.ts create mode 100644 packages/mailgun/src/models/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/override-properties/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/override-properties/override-properties.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/send-email-request-base/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/send-email-request-base/send-email-request-base.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/send-email-request/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/send-email-request/send-email-request.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/send-mime-email-request/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/send-mime-email-request/send-mime-email-request.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/template-properties/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/request/template-properties/template-properties.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/bad-request/bad-request.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/bad-request/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/email-not-found/email-not-found.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/email-not-found/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/exceeded-queue-quota/exceeded-queue-quota.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/exceeded-queue-quota/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/generic-response/generic-response.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/generic-response/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/get-email-response/get-email-response.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/get-email-response/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/queue-status-disabled-details/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/queue-status-disabled-details/queue-status-disabled-details.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/send-email-response/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/send-email-response/send-email-response.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/sending-queues-status-response/index.ts create mode 100644 packages/mailgun/src/models/v1/emails/response/sending-queues-status-response/sending-queues-status-response.ts create mode 100644 packages/mailgun/src/models/v1/index.ts create mode 100644 packages/mailgun/src/rest/index.ts create mode 100644 packages/mailgun/src/rest/v1/emails/emails-api.jest.fixture.ts create mode 100644 packages/mailgun/src/rest/v1/emails/emails-api.ts create mode 100644 packages/mailgun/src/rest/v1/emails/index.ts create mode 100644 packages/mailgun/src/rest/v1/index.ts create mode 100644 packages/mailgun/src/rest/v1/mailgun-domain-api.ts create mode 100644 packages/mailgun/src/rest/v1/mailgun-service.ts create mode 100644 packages/mailgun/tests/models/v1/emails/request/index.ts create mode 100644 packages/mailgun/tests/models/v1/emails/request/send-email-request.models.ts create mode 100644 packages/mailgun/tests/models/v1/emails/request/send-email-request.test.ts create mode 100644 packages/mailgun/tests/models/v1/emails/request/send-mime-email-request.models.ts create mode 100644 packages/mailgun/tests/models/v1/emails/request/send-mime-email-request.test.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/bad-request.models.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/bad-request.test.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/email-not-found.models.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/email-not-found.test.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/generic-response.models.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/generic-response.test.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/get-email-response.models.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/get-email-response.test.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/index.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/send-email-response.models.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/send-email-response.test.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/sending-queues-status-response.models.ts create mode 100644 packages/mailgun/tests/models/v1/emails/response/sending-queues-status-response.test.ts create mode 100644 packages/mailgun/tests/rest/v1/emails/emails-api.test.ts create mode 100644 packages/mailgun/tests/rest/v1/emails/emails.steps.ts create mode 100644 packages/mailgun/tests/rest/v1/mailgun-domain-api.test.ts create mode 100644 packages/mailgun/tests/rest/v1/mailgun-service.test.ts create mode 100644 packages/mailgun/tsconfig.build.json create mode 100644 packages/mailgun/tsconfig.json create mode 100644 packages/mailgun/tsconfig.tests.json diff --git a/.github/workflows/run-ci.yaml b/.github/workflows/run-ci.yaml index acf12067..f9d313e5 100644 --- a/.github/workflows/run-ci.yaml +++ b/.github/workflows/run-ci.yaml @@ -54,6 +54,7 @@ jobs: mkdir -p ./packages/sms/tests/e2e/features mkdir -p ./packages/verification/tests/e2e/features mkdir -p ./packages/voice/tests/e2e/features + mkdir -p ./packages/mailgun/tests/e2e/features - name: Copy feature files run: | @@ -64,6 +65,7 @@ jobs: cp sinch-sdk-mockserver/features/sms/*.feature ./packages/sms/tests/e2e/features/ cp sinch-sdk-mockserver/features/verification/*.feature ./packages/verification/tests/e2e/features/ cp sinch-sdk-mockserver/features/voice/*.feature ./packages/voice/tests/e2e/features/ + cp sinch-sdk-mockserver/features/mailgun/*.feature ./packages/mailgun/tests/e2e/features/ - name: Run e2e tests run: yarn run e2e diff --git a/examples/simple-examples/package.json b/examples/simple-examples/package.json index bda9051a..2a7785a1 100644 --- a/examples/simple-examples/package.json +++ b/examples/simple-examples/package.json @@ -118,6 +118,11 @@ "fax:fax-to-email:listNumbers": "ts-node src/fax/fax-to-email/listNumbers.ts", "fax:fax-to-email:update": "ts-node src/fax/fax-to-email/update.ts", "fax:fax-to-email:delete": "ts-node src/fax/fax-to-email/delete.ts", + "mailgun:email:sendEmail": "ts-node src/mailgun/emails/sendEmail.ts", + "mailgun:email:sendMimeEmail": "ts-node src/mailgun/emails/sendMimeEmail.ts", + "mailgun:email:getEmail": "ts-node src/mailgun/emails/getEmail.ts", + "mailgun:email:purgeDomainQueues": "ts-node src/mailgun/emails/purgeDomainQueues.ts", + "mailgun:email:getSendingQueuesStatus": "ts-node src/mailgun/emails/getSendingQueuesStatus.ts", "numbers:regions:list": "ts-node src/numbers/regions/list.ts", "numbers:available:list": "ts-node src/numbers/available/list.ts", "numbers:available:checkAvailability": "ts-node src/numbers/available/checkAvailability.ts", @@ -184,10 +189,12 @@ }, "dependencies": { "@sinch/sdk-core": "^1.2.0", - "dotenv": "^16.3.1" + "dotenv": "^16.3.1", + "nodemailer": "^6.9.16" }, "devDependencies": { "@types/node": "^20.8.7", + "@types/nodemailer": "^6.4.16", "ts-node": "^10.9.1", "typescript": "^5.2.2" } diff --git a/examples/simple-examples/src/config.ts b/examples/simple-examples/src/config.ts index 4ef27214..8476eabc 100644 --- a/examples/simple-examples/src/config.ts +++ b/examples/simple-examples/src/config.ts @@ -3,6 +3,7 @@ import { SmsRegion, ConversationService, FaxService, + MailgunService, NumbersService, SmsService, VerificationService, @@ -30,6 +31,15 @@ export const initFaxService = (): FaxService => { return initClient().fax; }; +const initMailgunClient = (): Pick => { + const mailgunApiKey = process.env.MAILGUN_API_KEY || ''; + return new SinchClient({ mailgunApiKey }); +}; + +export const initMailgunService = (): MailgunService => { + return initMailgunClient().mailgun; +}; + export const initNumbersService = (): NumbersService => { return initClient().numbers; }; @@ -219,6 +229,22 @@ export const getIpRangeIdFromConfig = () => { return readVariable('IP_RANGE_ID'); }; +export const getMailgunDomainFromConfig = () => { + return readVariable('MAILGUN_DOMAIN'); +}; + +export const getMailgunSenderFromConfig = () => { + return readVariable('MAILGUN_SENDER_ADDRESS'); +}; + +export const getMailgunRecipientFromConfig = () => { + return readVariable('MAILGUN_RECIPIENT_ADDRESS'); +}; + +export const getMailgunStorageKeyFromConfig = () => { + return readVariable('MAILGUN_STORAGE_KEY'); +}; + const readVariable = ( name: string): string => { const value = process.env[name]; if (!value) { diff --git a/examples/simple-examples/src/mailgun/emails/getEmail.ts b/examples/simple-examples/src/mailgun/emails/getEmail.ts new file mode 100644 index 00000000..b7fb424f --- /dev/null +++ b/examples/simple-examples/src/mailgun/emails/getEmail.ts @@ -0,0 +1,27 @@ +import { + getMailgunDomainFromConfig, + getMailgunStorageKeyFromConfig, + initMailgunService, + printFullResponse, +} from '../../config'; + +(async () => { + console.log('************'); + console.log('* GetEmail *'); + console.log('************'); + + const domainName = getMailgunDomainFromConfig(); + const storageKey = getMailgunStorageKeyFromConfig(); + + const mailgunService = initMailgunService(); + let response; + try { + response = await mailgunService.emails.getEmail(domainName, storageKey); + } catch (error) { + console.error('Error when retrieving a message'); + throw error; + } + + printFullResponse(response); + +})(); diff --git a/examples/simple-examples/src/mailgun/emails/getSendingQueuesStatus.ts b/examples/simple-examples/src/mailgun/emails/getSendingQueuesStatus.ts new file mode 100644 index 00000000..296a3431 --- /dev/null +++ b/examples/simple-examples/src/mailgun/emails/getSendingQueuesStatus.ts @@ -0,0 +1,21 @@ +import { getMailgunDomainFromConfig, initMailgunService, printFullResponse } from '../../config'; + +(async () => { + console.log('**************************'); + console.log('* GetSendingQueuesStatus *'); + console.log('**************************'); + + const domainName = getMailgunDomainFromConfig(); + + const mailgunService = initMailgunService(); + let response; + try { + response = await mailgunService.emails.getSendingQueuesStatus(domainName); + } catch (error) { + console.error('Error when fetching the sending queue status'); + throw error; + } + + printFullResponse(response); + +})(); diff --git a/examples/simple-examples/src/mailgun/emails/purgeDomainQueues.ts b/examples/simple-examples/src/mailgun/emails/purgeDomainQueues.ts new file mode 100644 index 00000000..524ea4b3 --- /dev/null +++ b/examples/simple-examples/src/mailgun/emails/purgeDomainQueues.ts @@ -0,0 +1,21 @@ +import { getMailgunDomainFromConfig, initMailgunService, printFullResponse } from '../../config'; + +(async () => { + console.log('*********************'); + console.log('* PurgeDomainQueues *'); + console.log('*********************'); + + const domainName = getMailgunDomainFromConfig(); + + const mailgunService = initMailgunService(); + let response; + try { + response = await mailgunService.emails.purgeDomainQueues(domainName); + } catch (error) { + console.error('Error when trying to purge the domain queues'); + throw error; + } + + printFullResponse(response); + +})(); diff --git a/examples/simple-examples/src/mailgun/emails/sendEmail.ts b/examples/simple-examples/src/mailgun/emails/sendEmail.ts new file mode 100644 index 00000000..1cf0509b --- /dev/null +++ b/examples/simple-examples/src/mailgun/emails/sendEmail.ts @@ -0,0 +1,37 @@ +import { + getMailgunDomainFromConfig, + getMailgunRecipientFromConfig, + getMailgunSenderFromConfig, + initMailgunService, + printFullResponse, +} from '../../config'; +import { Mailgun } from '@sinch/sdk-core'; + +(async () => { + console.log('*************'); + console.log('* SendEmail *'); + console.log('*************'); + + const domainName = getMailgunDomainFromConfig(); + const sender = getMailgunSenderFromConfig(); + const recipient = getMailgunRecipientFromConfig(); + + const requestData: Mailgun.SendEmailRequest = { + from: sender, + to: recipient, + subject: 'First email from the Node.js SDK', + html: 'Hello!
This is an email sent with the Node.js SDK.', + }; + + const mailgunService = initMailgunService(); + let response; + try { + response = await mailgunService.emails.sendEmail(domainName, requestData); + } catch (error) { + console.error('Error when sending a message'); + throw error; + } + + printFullResponse(response); + +})(); diff --git a/examples/simple-examples/src/mailgun/emails/sendMimeEmail.ts b/examples/simple-examples/src/mailgun/emails/sendMimeEmail.ts new file mode 100644 index 00000000..fea3dd30 --- /dev/null +++ b/examples/simple-examples/src/mailgun/emails/sendMimeEmail.ts @@ -0,0 +1,47 @@ +import { + getMailgunDomainFromConfig, + getMailgunRecipientFromConfig, + getMailgunSenderFromConfig, + initMailgunService, + printFullResponse, +} from '../../config'; +import { Mailgun } from '@sinch/sdk-core'; +import MailComposer from 'nodemailer/lib/mail-composer'; + +(async () => { + console.log('*****************'); + console.log('* SendMimeEmail *'); + console.log('*****************'); + + const domainName = getMailgunDomainFromConfig(); + const sender = getMailgunSenderFromConfig(); + const recipient = getMailgunRecipientFromConfig(); + + const data = { + message: { + from: sender, + subject: 'Test Sending mime messages from node', + text: 'This is a mime message', + html: 'HTML
version
of
the
body', + }, + }; + const mail = new MailComposer(data.message); + const compiledMessage = await mail.compile().build(); + + const requestData: Mailgun.SendMimeEmailRequest= { + to: recipient, + message: compiledMessage.toString(), + }; + + const mailgunService = initMailgunService(); + let response; + try { + response = await mailgunService.emails.sendMimeEmail(domainName, requestData); + } catch (error) { + console.error('Error when sending a message'); + throw error; + } + + printFullResponse(response); + +})(); diff --git a/jest.config.ts b/jest.config.ts index 4bf4fc90..a4d4be94 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -24,6 +24,11 @@ const config: Config.InitialOptions = { testMatch: ['/packages/fax/tests/**/*.test.ts'], coveragePathIgnorePatterns: ['node_modules', 'tests'], }, + { + displayName: 'Mailgun', + testMatch: ['/packages/mailgun/tests/**/*.test.ts'], + coveragePathIgnorePatterns: ['node_modules', 'tests'], + }, { displayName: 'Numbers', testMatch: ['/packages/numbers/tests/**/*.test.ts'], diff --git a/packages/mailgun/CHANGELOG.md b/packages/mailgun/CHANGELOG.md new file mode 100644 index 00000000..a23468f0 --- /dev/null +++ b/packages/mailgun/CHANGELOG.md @@ -0,0 +1,3 @@ +# Version X.Y.Z + + - Initial version diff --git a/packages/mailgun/README.md b/packages/mailgun/README.md new file mode 100644 index 00000000..1dfcd771 --- /dev/null +++ b/packages/mailgun/README.md @@ -0,0 +1,113 @@ +# Sinch Mailgun SDK for Node.js + +This package contains the Sinch Mailgun SDK for Node.js for use with [Sinch APIs](https://developers.sinch.com/). To use it, you will need a Sinch account. Please [sign up](https://dashboard.sinch.com/signup) or [log in](https://dashboard.sinch.com/login) if you already have one. + +In case Mailgun is not yet integrated with Sinch in your market, you will need to [create a Mailgun account](https://signup.mailgun.com/new/signup?) or [log in](https://login.mailgun.com/login/) if you already have one. + +## Installation + +We recommend to use this SDK as part of the `@sinch/sdk-core` package as it will take care about the authentication plugins to use. + +However, it's still possible to use this SDK standalone is you need to access the Mailgun API only. + +### With NPM + +```bash +npm install @sinch/mailgun +``` + +### With Yarn + +```bash +yarn add @sinch/mailgun +``` + +## Usage + +### Credentials + +The `Mailgun` API uses an API key to identify and authenticate the caller: it can be found in your [Mailgun Dashboard](https://app.mailgun.com/settings/api_security). + +### As part of the Sinch SDK + +If you are using this SDK as part of the Sinch SDK (`@sinch/sdk-core`) you can access it as the `mailgun` property of the client that you would have instantiated. + +```typescript +import { + Mailgun, + SinchClient, + SinchClientParameters, +} from '@sinch/sdk-core'; + +const credentials: SinchClientParameters = { + mailgunApiKey: 'MAILGUN_API_KEY', +}; + +const sinch = new SinchClient(credentials); + +const requestData: Mailgun.SendEmailRequest = { + from: 'sender@sinch.com', + to: 'recipient@sinch.com', + subject: 'First email from the Node.js SDK', + html: 'Hello!
This is an email sent with the Node.js SDK.', +}; + +// Access the 'mailgun' domain registered on the Sinch Client +const result: Mailgun.SendEmailResponse + = await sinch.mailgun.emails.sendEmail(requestData); +``` + +### Standalone + +The SDK can be used standalone if you need to use only the Mailgun APIs. + +```typescript +import { + Mailgun, + SinchClientParameters, +} from '@sinch/sdk-client'; +import { + +} from '@sinch/mailgun'; + +const credentials: SinchClientParameters = { + mailgunApiKey: 'MAILGUN_API_KEY', +}; + +// Declare the 'mailgun' service in a standalone way +const mailgun = new Mailgun(credentials); + +const requestData: Mailgun.SendEmailRequest = { + from: 'sender@sinch.com', + to: 'recipient@sinch.com', + subject: 'First email from the Node.js SDK', + html: 'Hello!
This is an email sent with the Node.js SDK.', +}; + +// Use the standalone declaration of the 'mailgun' domain +const result: Mailgun.SendEmailResponse + = await mailgun.emails.sendEmail(requestData); +``` + +## Promises + +All the methods that interact with the Sinch APIs use Promises. You can use `await` in an `async` method to wait for the response or you can resolve them yourself with `then()` / `catch()`. + +```typescript +// Method 1: Wait for the Promise to complete +let sendEmailResponse: Mailgun.SendEmailResponse; +try { + sendEmailResponse = await sinch.mailgun.emails.sendEmail(requestData); + console.log(`Message id = ${sendEmailResponse.id}`); +} catch (error: any) { + console.error(`ERROR ${error.statusCode}: `); +} + +// Method 2: Resolve the promise +sinch.mailgun.emails.sendEmail(requestData) + .then(response => console.log(`Message id = ${response.id}`)) + .catch(error => console.error(`ERROR ${error.statusCode}: `)); +``` + +## Contact +Developer Experience team: [devexp@sinch.com](mailto:devexp@sinch.com) diff --git a/packages/mailgun/cucumber.js b/packages/mailgun/cucumber.js new file mode 100644 index 00000000..691a9809 --- /dev/null +++ b/packages/mailgun/cucumber.js @@ -0,0 +1,8 @@ +module.exports = { + default: [ + 'tests/e2e/features/**/*.feature', + '--require-module ts-node/register', + '--require tests/rest/v1/**/*.steps.ts', + `--format-options '{"snippetInterface": "synchronous"}'`, + ].join(' '), +}; diff --git a/packages/mailgun/package.json b/packages/mailgun/package.json new file mode 100644 index 00000000..4f0834aa --- /dev/null +++ b/packages/mailgun/package.json @@ -0,0 +1,38 @@ +{ + "name": "@sinch/mailgun", + "version": "1.2.0", + "description": "Sinch Mailgun API", + "homepage": "", + "repository": { + "type": "git", + "url": "" + }, + "license": "Apache-2.0", + "author": "Sinch", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.js" + } + }, + "directories": { + "src": "dist", + "test": "tests" + }, + "files": ["/dist"], + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf dist tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo", + "compile": "tsc -p tsconfig.build.json && tsc -p tsconfig.tests.json && rimraf dist/tests tsconfig.build.tsbuildinfo", + "test:e2e": "cucumber-js" + }, + "dependencies": { + "@sinch/sdk-client": "^1.2.0" + }, + "devDependencies": {}, + "publishConfig": { + "directory": "dist" + } +} diff --git a/packages/mailgun/src/index.ts b/packages/mailgun/src/index.ts new file mode 100644 index 00000000..d2588434 --- /dev/null +++ b/packages/mailgun/src/index.ts @@ -0,0 +1,3 @@ +export * as Mailgun from './models'; +export * from './rest'; +export * from '@sinch/sdk-client'; diff --git a/packages/mailgun/src/models/index.ts b/packages/mailgun/src/models/index.ts new file mode 100644 index 00000000..5b98253d --- /dev/null +++ b/packages/mailgun/src/models/index.ts @@ -0,0 +1 @@ +export * from './v1'; diff --git a/packages/mailgun/src/models/v1/emails/request/override-properties/index.ts b/packages/mailgun/src/models/v1/emails/request/override-properties/index.ts new file mode 100644 index 00000000..94d31285 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/override-properties/index.ts @@ -0,0 +1 @@ +export type { OverrideProperties } from './override-properties'; diff --git a/packages/mailgun/src/models/v1/emails/request/override-properties/override-properties.ts b/packages/mailgun/src/models/v1/emails/request/override-properties/override-properties.ts new file mode 100644 index 00000000..ada5feb7 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/override-properties/override-properties.ts @@ -0,0 +1,34 @@ +export interface OverrideProperties { + /** Tag string. See **Tagging** for more information */ + tag?: string; + /** Toggles Send Time Optimization (STO) on a per-message basis. String should be set to the number of hours in `[0-9]+h` format, with the minimum being `24h` and the maximum being `72h`. This value defines the time window in which Mailgun will run the optimization algorithm based on prior engagement data of a given recipient. See **Sending a Message with STO** for details. *Please note that STO is only available on certain plans. See www.mailgun.com/pricing for more info* */ + deliveryTimeOptimizePeriod?: string; + /** Enables/disables DKIM signatures on a per-message basis. Pass `yes` or `no`, `true` or `false` */ + enableDkimSignature?: string; + /** Specify a second domain key to sign the email with. The value is formatted as `signing_domain/selector`, e.g. `example.com/s1`. This tells Mailgun to sign the message with the signing domain `example.com` using the selector `s1`. Note: the domain key specified must have been previously created and activated. */ + secondaryDkim?: string; + /** Specify an alias of the domain key specified in `o:secondary-dkim`. Also formatted as `public_signing_domain/selector`. `o:secondary-dkim` option must also be provided. Mailgun will sign the message with the provided key of the secondary DKIM, but use the public secondary DKIM name and selector. Note: We will perform a DNS check prior to signing the message to ensure the public keys matches the secondary DKIM. */ + secondaryDkimPublic?: string; + /** Specifies the scheduled delivery time in RFC-2822 format (https://mailgun-docs.redoc.ly/docs/mailgun/api-reference/intro/#date-format). Depending on your plan, you can schedule messages up to 3 or 7 days in advance. If your domain has a custom message_ttl (time-to-live) setting, this value determines the maximum scheduling duration. */ + deliveryTime?: string; + /** Toggles Timezone Optimization (TZO) on a per message basis. String should be set to preferred delivery time in `HH:mm` or `hh:mmaa` format, where `HH:mm` is used for 24 hour format without AM/PM and hh:mmaa is used for 12 hour format with AM/PM. See **Sending a Message with TZO** for details. *Please note that TZO is only available on certain plans. See www.mailgun.com/pricing for more info* */ + timeZoneLocalize?: string; + /** Toggles click tracking on a per-message basis. Has higher priority than domain-level setting. Pass `yes` or `no`, `true` or `false`, or `htmlonly` if you only want links rewritten in the HTML part of the message */ + trackingClicks?: string; + /** Toggles both click and open tracking on a per-message basis, see **Tracking Messages** for details. Pass `yes` or `no`, `true` or `false`, or `htmlonly` if you only want links rewritten in the HTML part of the message */ + tracking?: string; + /** Toggles opens tracking on a per-message basis. Has higher priority than domain-level setting. Pass `yes` or `no`, `true` or `false` */ + trackingOpens?: string; + /** If you send long emails that experience truncation or other rendering issues at the recipient, you can ensure opens are being tracked accurately with placement of the tracking pixel at the top of your emails */ + trackingPixelLocationTop?: string; + /** Used to specify an IP Address to send an email that is owned by your account */ + sendingIp?: string; + /** If an IP Pool ID is provided, the email will be delivered with an IP that belongs in that pool */ + sendingIpPool?: string; + /** If set to `true` or `yes` this requires the message only be sent over a TLS connection. If a TLS connection can not be established, Mailgun will not deliver the message. If set to `false` or `no`, Mailgun will still try and upgrade the connection, but if Mailgun cannot, the message will be delivered over a plaintext SMTP connection. The default is `false` */ + requireTls?: string; + /** If set to `true` or `yes`, the certificate and hostname of the resolved MX Host will not be verified when trying to establish a TLS connection. If set to `false` or `no`, Mailgun will verify the certificate and hostname. If either one can not be verified, a TLS connection will not be established. The default is `false` */ + skipVerification?: string; + /** Enables sending in test mode. Pass `yes` if needed. See **Sending in Test Mode** */ + isTestMode?: boolean; +} diff --git a/packages/mailgun/src/models/v1/emails/request/send-email-request-base/index.ts b/packages/mailgun/src/models/v1/emails/request/send-email-request-base/index.ts new file mode 100644 index 00000000..eb074f29 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/send-email-request-base/index.ts @@ -0,0 +1 @@ +export type { SendEmailRequestBase } from './send-email-request-base'; diff --git a/packages/mailgun/src/models/v1/emails/request/send-email-request-base/send-email-request-base.ts b/packages/mailgun/src/models/v1/emails/request/send-email-request-base/send-email-request-base.ts new file mode 100644 index 00000000..962fa37a --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/send-email-request-base/send-email-request-base.ts @@ -0,0 +1,23 @@ +import { OverrideProperties } from '../override-properties'; +import { TemplateProperties } from '../template-properties'; + +export interface SendEmailRequestBase { + /** Email address of the recipient(s). Example: `\"Bob \"`. You can use commas to separate multiple recipients */ + to?: string; + /** Email address for `From` header */ + from?: string; + /** Same as `To` but for `Cc` */ + cc?: string; + /** Same as `To` but for `Bcc` */ + bcc?: string; + /** Message subject */ + subject?: string; + /** File attachment. You can post multiple `attachment` values. **Important:** You must use `multipart/form-data` encoding for sending attachments */ + attachment?: string; + /** Attachment with `inline` disposition. Can be used to send inline images (see example). You can post multiple `inline` values */ + inline?: string; + /** @see TemplateProperties */ + templateProperties?: TemplateProperties; + /** @see OverrideProperties */ + overrideProperties?: OverrideProperties; +} diff --git a/packages/mailgun/src/models/v1/emails/request/send-email-request/index.ts b/packages/mailgun/src/models/v1/emails/request/send-email-request/index.ts new file mode 100644 index 00000000..cf7e8c76 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/send-email-request/index.ts @@ -0,0 +1 @@ +export type { SendEmailRequest } from './send-email-request'; diff --git a/packages/mailgun/src/models/v1/emails/request/send-email-request/send-email-request.ts b/packages/mailgun/src/models/v1/emails/request/send-email-request/send-email-request.ts new file mode 100644 index 00000000..a57365f7 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/send-email-request/send-email-request.ts @@ -0,0 +1,166 @@ +import { TemplateProperties } from '../template-properties'; +import { OverrideProperties } from '../override-properties'; +import FormData = require('form-data'); + +export type MessageContentWhereHtmlContentCanBeInlineOnly = { + /** Body of the message (HTML version) */ + html?: string; + /** AMP part of the message. Please follow Google guidelines to compose and send AMP emails */ + amp_html?: string; + /** Body of the message (text version) */ + text?: string; + /** Name of a template stored via template API to use to render the email body. See **Templates** for more information */ + template?: never; +} & CommonEmailProperties; + +export type MessageContentWhereHtmlContentCanBeFromTemplateOnly = { + /** Name of a template stored via template API to use to render the email body. See **Templates** for more information */ + template?: string; + /** AMP part of the message. Please follow Google guidelines to compose and send AMP emails */ + amp_html?: string; + /** Body of the message (text version) */ + text?: string; + /** Body of the message (HTML version) */ + html?: never; +} & CommonEmailProperties; + +export interface CommonEmailProperties { + /** Email address of the recipient(s). Example: `\"Bob \"`. You can use commas to separate multiple recipients */ + to: string; + /** Email address for `From` header */ + from: string; + /** Same as `To` but for `Cc` */ + cc?: string; + /** Same as `To` but for `Bcc` */ + bcc?: string; + /** Message subject */ + subject: string; + /** File attachment. You can post multiple `attachment` values. **Important:** You must use `multipart/form-data` encoding for sending attachments */ + attachment?: string; + /** Attachment with `inline` disposition. Can be used to send inline images (see example). You can post multiple `inline` values */ + inline?: string; + /** @see TemplateProperties */ + templateProperties?: TemplateProperties; + /** @see OverrideProperties */ + overrideProperties?: OverrideProperties; + /** h: prefix followed by a Header/Value pair. For example: h:X-Mailgun-Sending-Ip-Pool=xx.xx.xxx.x. */ + [key: `h:${string}`]: string; + /** `v:` prefix followed by an arbitrary name allows to attach a custom JSON data to the message. See **Attaching Data to Messages** for more information */ + [key: `v:${string}`]: string | number; +} + +export type SendEmailRequest = MessageContentWhereHtmlContentCanBeInlineOnly + | MessageContentWhereHtmlContentCanBeFromTemplateOnly; + +export const transformSendEmailRequestIntoApiRequestBody = (sdkRequest: SendEmailRequest): FormData => { + const formData = new FormData(); + if ('html' in sdkRequest && sdkRequest['html'] !== undefined && sdkRequest['html'] !== null) { + formData.append('html', sdkRequest['html']); + } + if ('template' in sdkRequest && sdkRequest['template'] !== undefined && sdkRequest['template'] !== null) { + formData.append('template', sdkRequest['template']); + } + if (sdkRequest['amp_html'] !== undefined && sdkRequest['amp_html'] !== null) { + formData.append('amp-html', sdkRequest['amp_html']); + } + if (sdkRequest['text'] !== undefined && sdkRequest['text'] !== null) { + formData.append('text', sdkRequest['text']); + } + if (sdkRequest['to'] !== undefined && sdkRequest['to'] !== null) { + formData.append('to', sdkRequest['to']); + } + if (sdkRequest['from'] !== undefined && sdkRequest['from'] !== null) { + formData.append('from', sdkRequest['from']); + } + if (sdkRequest['cc'] !== undefined && sdkRequest['cc'] !== null) { + formData.append('cc', sdkRequest['cc']); + } + if (sdkRequest['bcc'] !== undefined && sdkRequest['bcc'] !== null) { + formData.append('bcc', sdkRequest['bcc']); + } + if (sdkRequest['subject'] !== undefined && sdkRequest['subject'] !== null) { + formData.append('subject', sdkRequest['subject']); + } + if (sdkRequest['attachment'] !== undefined && sdkRequest['attachment'] !== null) { + formData.append('attachment', sdkRequest['attachment']); + } + if (sdkRequest['inline'] !== undefined && sdkRequest['inline'] !== null) { + formData.append('inline', sdkRequest['inline']); + } + if (sdkRequest['templateProperties'] !== undefined && sdkRequest['templateProperties'] !== null) { + const templateProperties = sdkRequest['templateProperties']; + if (templateProperties['text'] !== undefined && templateProperties['text'] !== null) { + formData.append('t:text', templateProperties['text']); + } + if (templateProperties['version'] !== undefined && templateProperties['version'] !== null) { + formData.append('t:version', templateProperties['version']); + } + if (templateProperties['variables'] !== undefined && templateProperties['variables'] !== null) { + formData.append('t:variables', templateProperties['variables']); + } + } + if (sdkRequest['overrideProperties'] !== undefined && sdkRequest['overrideProperties'] !== null) { + const overrideProperties = sdkRequest['overrideProperties']; + if (overrideProperties['tag'] !== undefined && overrideProperties['tag'] !== null) { + formData.append('o:tag', overrideProperties['tag']); + } + if (overrideProperties['deliveryTimeOptimizePeriod'] !== undefined + && overrideProperties['deliveryTimeOptimizePeriod'] !== null) { + formData.append('o:deliverytime-optimize-period', overrideProperties['deliveryTimeOptimizePeriod']); + } + if (overrideProperties['enableDkimSignature'] !== undefined && overrideProperties['enableDkimSignature'] !== null) { + formData.append('o:dkim', overrideProperties['enableDkimSignature']); + } + if (overrideProperties['secondaryDkim'] !== undefined && overrideProperties['secondaryDkim'] !== null) { + formData.append('o:secondary-dkim', overrideProperties['secondaryDkim']); + } + if (overrideProperties['secondaryDkimPublic'] !== undefined && overrideProperties['secondaryDkimPublic'] !== null) { + formData.append('o:secondary-dkim-public', overrideProperties['secondaryDkimPublic']); + } + if (overrideProperties['deliveryTime'] !== undefined && overrideProperties['deliveryTime'] !== null) { + formData.append('o:deliverytime', overrideProperties['deliveryTime']); + } + if (overrideProperties['timeZoneLocalize'] !== undefined && overrideProperties['timeZoneLocalize'] !== null) { + formData.append('o:time-zone-localize', overrideProperties['timeZoneLocalize']); + } + if (overrideProperties['tracking'] !== undefined && overrideProperties['tracking'] !== null) { + formData.append('o:tracking', overrideProperties['tracking']); + } + if (overrideProperties['trackingClicks'] !== undefined && overrideProperties['trackingClicks'] !== null) { + formData.append('o:tracking-clicks', overrideProperties['trackingClicks']); + } + if (overrideProperties['trackingOpens'] !== undefined && overrideProperties['trackingOpens'] !== null) { + formData.append('o:tracking-opens', overrideProperties['trackingOpens']); + } + if (overrideProperties['trackingPixelLocationTop'] !== undefined + && overrideProperties['trackingPixelLocationTop'] !== null) { + formData.append('o:tracking-pixel-location-top', overrideProperties['trackingPixelLocationTop']); + } + if (overrideProperties['sendingIp'] !== undefined && overrideProperties['sendingIp'] !== null) { + formData.append('o:sending-ip', overrideProperties['sendingIp']); + } + if (overrideProperties['sendingIpPool'] !== undefined && overrideProperties['sendingIpPool'] !== null) { + formData.append('o:sending-ip-pool', overrideProperties['sendingIpPool']); + } + if (overrideProperties['requireTls'] !== undefined && overrideProperties['requireTls'] !== null) { + formData.append('o:require-tls', overrideProperties['requireTls']); + } + if (overrideProperties['skipVerification'] !== undefined && overrideProperties['skipVerification'] !== null) { + formData.append('o:skip-verification', overrideProperties['skipVerification']); + } + if (overrideProperties['isTestMode'] !== undefined && overrideProperties['isTestMode'] !== null) { + formData.append('o:testmode', overrideProperties['isTestMode']); + } + } + addPropertiesToFormData(sdkRequest, 'h:', formData); + addPropertiesToFormData(sdkRequest, 'v:', formData); + return formData; +}; + +const addPropertiesToFormData = (obj: SendEmailRequest, prefix: string, formData: FormData) => { + for (const [key, value] of Object.entries(obj)) { + if (key.startsWith(prefix) && !!value) { + formData.append(key, value); + } + } +}; diff --git a/packages/mailgun/src/models/v1/emails/request/send-mime-email-request/index.ts b/packages/mailgun/src/models/v1/emails/request/send-mime-email-request/index.ts new file mode 100644 index 00000000..22f54c29 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/send-mime-email-request/index.ts @@ -0,0 +1 @@ +export type { SendMimeEmailRequest } from './send-mime-email-request'; diff --git a/packages/mailgun/src/models/v1/emails/request/send-mime-email-request/send-mime-email-request.ts b/packages/mailgun/src/models/v1/emails/request/send-mime-email-request/send-mime-email-request.ts new file mode 100644 index 00000000..ac66408e --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/send-mime-email-request/send-mime-email-request.ts @@ -0,0 +1,113 @@ +import { TemplateProperties } from '../template-properties'; +import { OverrideProperties } from '../override-properties'; +import FormData = require('form-data'); + +export interface SendMimeEmailRequest { + /** MIME string of the message. Make sure to use `multipart/form-data` content type to send this as a file upload */ + message: string | Blob | Buffer | NodeJS.ReadableStream; + /** Email address of the recipient(s). Example: `\"Bob \"`. You can use commas to separate multiple recipients */ + to: string; + /** Name of a template stored via template API to use to render the email body. See **Templates** for more information */ + template?: string; + /** @see TemplateProperties */ + templateProperties?: TemplateProperties; + /** @see OverrideProperties */ + overrideProperties?: OverrideProperties; + /** A valid JSON-encoded dictionary, where key is a plain recipient address and value is a dictionary with variables that can be referenced in the message body. See **Batch Sending** for more information */ + recipientVariables?: string; + /** h: prefix followed by a Header/Value pair. For example: h:X-Mailgun-Sending-Ip-Pool=xx.xx.xxx.x. */ + [key: `h:${string}`]: string; + /** `v:` prefix followed by an arbitrary name allows to attach a custom JSON data to the message. See **Attaching Data to Messages** for more information */ + [key: `v:${string}`]: string | number; +} + +export const transformSendMimeEmailRequestIntoApiRequestBody = (sdkRequest: SendMimeEmailRequest): FormData => { + const formData = new FormData(); + if (sdkRequest['to'] !== undefined && sdkRequest['to'] !== null) { + formData.append('to', sdkRequest['to']); + } + if ('message' in sdkRequest && sdkRequest['message'] !== undefined && sdkRequest['template'] !== null) { + formData.append('message', sdkRequest['message'], { + filename: 'MimeMessage', + }); + } + if ('template' in sdkRequest && sdkRequest['template'] !== undefined && sdkRequest['template'] !== null) { + formData.append('template', sdkRequest['template']); + } + if (sdkRequest['templateProperties'] !== undefined && sdkRequest['templateProperties'] !== null) { + const templateProperties = sdkRequest['templateProperties']; + if (templateProperties['text'] !== undefined && templateProperties['text'] !== null) { + formData.append('t:text', templateProperties['text']); + } + if (templateProperties['version'] !== undefined && templateProperties['version'] !== null) { + formData.append('t:version', templateProperties['version']); + } + if (templateProperties['variables'] !== undefined && templateProperties['variables'] !== null) { + formData.append('t:variables', templateProperties['variables']); + } + } + if (sdkRequest['overrideProperties'] !== undefined && sdkRequest['overrideProperties'] !== null) { + const overrideProperties = sdkRequest['overrideProperties']; + if (overrideProperties['tag'] !== undefined && overrideProperties['tag'] !== null) { + formData.append('o:tag', overrideProperties['tag']); + } + if (overrideProperties['deliveryTimeOptimizePeriod'] !== undefined + && overrideProperties['deliveryTimeOptimizePeriod'] !== null) { + formData.append('o:deliverytime-optimize-period', overrideProperties['deliveryTimeOptimizePeriod']); + } + if (overrideProperties['enableDkimSignature'] !== undefined && overrideProperties['enableDkimSignature'] !== null) { + formData.append('o:dkim', overrideProperties['enableDkimSignature']); + } + if (overrideProperties['secondaryDkim'] !== undefined && overrideProperties['secondaryDkim'] !== null) { + formData.append('o:secondary-dkim', overrideProperties['secondaryDkim']); + } + if (overrideProperties['secondaryDkimPublic'] !== undefined && overrideProperties['secondaryDkimPublic'] !== null) { + formData.append('o:secondary-dkim-public', overrideProperties['secondaryDkimPublic']); + } + if (overrideProperties['deliveryTime'] !== undefined && overrideProperties['deliveryTime'] !== null) { + formData.append('o:deliverytime', overrideProperties['deliveryTime']); + } + if (overrideProperties['timeZoneLocalize'] !== undefined && overrideProperties['timeZoneLocalize'] !== null) { + formData.append('o:time-zone-localize', overrideProperties['timeZoneLocalize']); + } + if (overrideProperties['tracking'] !== undefined && overrideProperties['tracking'] !== null) { + formData.append('o:tracking', overrideProperties['tracking']); + } + if (overrideProperties['trackingClicks'] !== undefined && overrideProperties['trackingClicks'] !== null) { + formData.append('o:tracking-clicks', overrideProperties['trackingClicks']); + } + if (overrideProperties['trackingOpens'] !== undefined && overrideProperties['trackingOpens'] !== null) { + formData.append('o:tracking-opens', overrideProperties['trackingOpens']); + } + if (overrideProperties['trackingPixelLocationTop'] !== undefined + && overrideProperties['trackingPixelLocationTop'] !== null) { + formData.append('o:tracking-pixel-location-top', overrideProperties['trackingPixelLocationTop']); + } + if (overrideProperties['sendingIp'] !== undefined && overrideProperties['sendingIp'] !== null) { + formData.append('o:sending-ip', overrideProperties['sendingIp']); + } + if (overrideProperties['sendingIpPool'] !== undefined && overrideProperties['sendingIpPool'] !== null) { + formData.append('o:sending-ip-pool', overrideProperties['sendingIpPool']); + } + if (overrideProperties['requireTls'] !== undefined && overrideProperties['requireTls'] !== null) { + formData.append('o:require-tls', overrideProperties['requireTls']); + } + if (overrideProperties['skipVerification'] !== undefined && overrideProperties['skipVerification'] !== null) { + formData.append('o:skip-verification', overrideProperties['skipVerification']); + } + if (overrideProperties['isTestMode'] !== undefined && overrideProperties['isTestMode'] !== null) { + formData.append('o:testmode', overrideProperties['isTestMode']); + } + } + addPropertiesToFormData(sdkRequest, 'h:', formData); + addPropertiesToFormData(sdkRequest, 'v:', formData); + return formData; +}; + +const addPropertiesToFormData = (obj: SendMimeEmailRequest, prefix: string, formData: FormData) => { + for (const [key, value] of Object.entries(obj)) { + if (key.startsWith(prefix) && !!value) { + formData.append(key, value); + } + } +}; diff --git a/packages/mailgun/src/models/v1/emails/request/template-properties/index.ts b/packages/mailgun/src/models/v1/emails/request/template-properties/index.ts new file mode 100644 index 00000000..8e761a97 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/template-properties/index.ts @@ -0,0 +1 @@ +export type { TemplateProperties } from './template-properties'; diff --git a/packages/mailgun/src/models/v1/emails/request/template-properties/template-properties.ts b/packages/mailgun/src/models/v1/emails/request/template-properties/template-properties.ts new file mode 100644 index 00000000..1bb35243 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/request/template-properties/template-properties.ts @@ -0,0 +1,8 @@ +export interface TemplateProperties { + /** Pass `yes` if you want to have a rendered template in the text part of the message in case of template sending */ + text?: string; + /** Render a specific version of the given template instead of the latest version. `o:template` option must also be provided. */ + version?: string; + /** A valid JSON-encoded dictionary used as the input for template variable expansion. See **Templates** for more information */ + variables?: string; +} diff --git a/packages/mailgun/src/models/v1/emails/response/bad-request/bad-request.ts b/packages/mailgun/src/models/v1/emails/response/bad-request/bad-request.ts new file mode 100644 index 00000000..80dd82ae --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/bad-request/bad-request.ts @@ -0,0 +1,13 @@ +export const transformBadRequestIntoClientResponse = ( + apiResponse: BadRequestFromApi, +): BadRequest => { + const { + ...response + } = apiResponse; + return response; +}; + +export type BadRequest = Omit; +export interface BadRequestFromApi { + message: string; +} diff --git a/packages/mailgun/src/models/v1/emails/response/bad-request/index.ts b/packages/mailgun/src/models/v1/emails/response/bad-request/index.ts new file mode 100644 index 00000000..95242d8a --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/bad-request/index.ts @@ -0,0 +1 @@ +export type { BadRequest } from './bad-request'; diff --git a/packages/mailgun/src/models/v1/emails/response/email-not-found/email-not-found.ts b/packages/mailgun/src/models/v1/emails/response/email-not-found/email-not-found.ts new file mode 100644 index 00000000..733d835c --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/email-not-found/email-not-found.ts @@ -0,0 +1,14 @@ +export const transformEmailNotFoundIntoClientResponse = ( + apiResponse: EmailNotFoundFromApi, +): EmailNotFound => { + const { + ...response + } = apiResponse; + return response; +}; + +export type EmailNotFound = Omit; + +export interface EmailNotFoundFromApi { + message: string; +} diff --git a/packages/mailgun/src/models/v1/emails/response/email-not-found/index.ts b/packages/mailgun/src/models/v1/emails/response/email-not-found/index.ts new file mode 100644 index 00000000..6ed45d38 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/email-not-found/index.ts @@ -0,0 +1 @@ +export type { EmailNotFound } from './email-not-found'; diff --git a/packages/mailgun/src/models/v1/emails/response/exceeded-queue-quota/exceeded-queue-quota.ts b/packages/mailgun/src/models/v1/emails/response/exceeded-queue-quota/exceeded-queue-quota.ts new file mode 100644 index 00000000..17b07299 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/exceeded-queue-quota/exceeded-queue-quota.ts @@ -0,0 +1,22 @@ +import { QueueStatusDisabledDetails } from '../queue-status-disabled-details'; + +export const transformExceededQueueQuota = ( + apiResponse: ExceededQueueQuotaFromApi, +): ExceededQueueQuota => { + return { + disabled: apiResponse.disabled, + isDisabled: apiResponse.is_disabled, + }; +}; + +export interface ExceededQueueQuota { + /** @see QueueStatusDisabledDetails */ + disabled?: QueueStatusDisabledDetails; + isDisabled: boolean; +} + +export interface ExceededQueueQuotaFromApi { + /** @see QueueStatusDisabledDetails */ + disabled?: QueueStatusDisabledDetails; + is_disabled: boolean; +} diff --git a/packages/mailgun/src/models/v1/emails/response/exceeded-queue-quota/index.ts b/packages/mailgun/src/models/v1/emails/response/exceeded-queue-quota/index.ts new file mode 100644 index 00000000..f16dfc1a --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/exceeded-queue-quota/index.ts @@ -0,0 +1 @@ +export type { ExceededQueueQuota } from './exceeded-queue-quota'; diff --git a/packages/mailgun/src/models/v1/emails/response/generic-response/generic-response.ts b/packages/mailgun/src/models/v1/emails/response/generic-response/generic-response.ts new file mode 100644 index 00000000..f475b1ef --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/generic-response/generic-response.ts @@ -0,0 +1,14 @@ +export const transformGenericResponseIntoClientResponse = ( + apiResponse: GenericResponseFromApi, +): GenericResponse => { + const { + ...response + } = apiResponse; + return response; +}; + +export type GenericResponse = Omit + +export interface GenericResponseFromApi { + message: string; +} diff --git a/packages/mailgun/src/models/v1/emails/response/generic-response/index.ts b/packages/mailgun/src/models/v1/emails/response/generic-response/index.ts new file mode 100644 index 00000000..f1e9e87c --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/generic-response/index.ts @@ -0,0 +1 @@ +export type { GenericResponse } from './generic-response'; diff --git a/packages/mailgun/src/models/v1/emails/response/get-email-response/get-email-response.ts b/packages/mailgun/src/models/v1/emails/response/get-email-response/get-email-response.ts new file mode 100644 index 00000000..f3c6c3b0 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/get-email-response/get-email-response.ts @@ -0,0 +1,58 @@ +export const transformGetEmailResponseIntoClientResponse = (apiResponse: GetEmailResponseFromApi): GetEmailResponse => { + return { + contentTransferEncoding: apiResponse['Content-Transfer-Encoding'], + contentType: apiResponse['Content-Type'], + messageId: apiResponse['Message-Id'], + mimeVersion: apiResponse['Mime-Version'], + to: apiResponse['To'], + deliveryTime: apiResponse['X-Mailgun-Deliver-By'], + sender: apiResponse['sender'], + recipients: apiResponse['recipients'], + from: apiResponse['from'], + subject: apiResponse['subject'], + bodyHtml: apiResponse['body-html'], + bodyPlain: apiResponse['body-plain'], + strippedHtml: apiResponse['stripped-html'], + strippedText: apiResponse['stripped-text'], + strippedSignature: apiResponse['stripped-signature'], + }; +}; + +export interface GetEmailResponse { + contentTransferEncoding?: string, + contentType: string, + messageId: string, + mimeVersion: string, + to: string, + deliveryTime: Date, + sender: string, + recipients: string, + from: string, + subject: string + bodyHtml?: string, + bodyPlain: string, + strippedHtml: string, + strippedText: string, + strippedSignature: string, +} + +export interface GetEmailResponseFromApi { + 'Content-Transfer-Encoding'?: string; + 'Content-Type': string; + From: string; + 'Message-Id': string; + 'Mime-Version': string; + Subject: string; + To: string; + 'X-Mailgun-Deliver-By': Date; + sender: string; + recipients: string; + from: string; + subject: string; + 'body-html'?: string; + 'body-plain': string; + 'message-headers': Array>; + 'stripped-html': string; + 'stripped-text': string; + 'stripped-signature': string; +} diff --git a/packages/mailgun/src/models/v1/emails/response/get-email-response/index.ts b/packages/mailgun/src/models/v1/emails/response/get-email-response/index.ts new file mode 100644 index 00000000..28484aee --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/get-email-response/index.ts @@ -0,0 +1 @@ +export type { GetEmailResponse } from './get-email-response'; diff --git a/packages/mailgun/src/models/v1/emails/response/queue-status-disabled-details/index.ts b/packages/mailgun/src/models/v1/emails/response/queue-status-disabled-details/index.ts new file mode 100644 index 00000000..d6c63da2 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/queue-status-disabled-details/index.ts @@ -0,0 +1 @@ +export type { QueueStatusDisabledDetails } from './queue-status-disabled-details'; diff --git a/packages/mailgun/src/models/v1/emails/response/queue-status-disabled-details/queue-status-disabled-details.ts b/packages/mailgun/src/models/v1/emails/response/queue-status-disabled-details/queue-status-disabled-details.ts new file mode 100644 index 00000000..5a302a1b --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/queue-status-disabled-details/queue-status-disabled-details.ts @@ -0,0 +1,4 @@ +export interface QueueStatusDisabledDetails { + reason: string; + until: string; +} diff --git a/packages/mailgun/src/models/v1/emails/response/send-email-response/index.ts b/packages/mailgun/src/models/v1/emails/response/send-email-response/index.ts new file mode 100644 index 00000000..0afd055b --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/send-email-response/index.ts @@ -0,0 +1 @@ +export type { SendEmailResponse } from './send-email-response'; diff --git a/packages/mailgun/src/models/v1/emails/response/send-email-response/send-email-response.ts b/packages/mailgun/src/models/v1/emails/response/send-email-response/send-email-response.ts new file mode 100644 index 00000000..eedfbc21 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/send-email-response/send-email-response.ts @@ -0,0 +1,15 @@ +export const transformSendEmailResponseIntoClientResponse = ( + apiResponse: SendEmailResponseFromApi, +): SendEmailResponse => { + const { + ...response + } = apiResponse; + return response; +}; + +export type SendEmailResponse = Omit + +export interface SendEmailResponseFromApi { + message: string; + id: string; +} diff --git a/packages/mailgun/src/models/v1/emails/response/sending-queues-status-response/index.ts b/packages/mailgun/src/models/v1/emails/response/sending-queues-status-response/index.ts new file mode 100644 index 00000000..9a8adf99 --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/sending-queues-status-response/index.ts @@ -0,0 +1 @@ +export type { SendingQueuesStatusResponse } from './sending-queues-status-response'; diff --git a/packages/mailgun/src/models/v1/emails/response/sending-queues-status-response/sending-queues-status-response.ts b/packages/mailgun/src/models/v1/emails/response/sending-queues-status-response/sending-queues-status-response.ts new file mode 100644 index 00000000..ba92da4a --- /dev/null +++ b/packages/mailgun/src/models/v1/emails/response/sending-queues-status-response/sending-queues-status-response.ts @@ -0,0 +1,25 @@ +import { ExceededQueueQuota } from '../exceeded-queue-quota'; +import { ExceededQueueQuotaFromApi, transformExceededQueueQuota } from '../exceeded-queue-quota/exceeded-queue-quota'; + +export const transformSendingQueuesStatusResponseIntoClientResponse = ( + apiResponse: SendingQueuesStatusResponseFromApi, +): SendingQueuesStatusResponse => { + return { + scheduled: transformExceededQueueQuota(apiResponse.scheduled), + regular: transformExceededQueueQuota(apiResponse.regular), + }; +}; + +export interface SendingQueuesStatusResponse { + /** @see ExceededQueueQuota */ + scheduled: ExceededQueueQuota; + /** @see ExceededQueueQuota */ + regular: ExceededQueueQuota; +} + +export interface SendingQueuesStatusResponseFromApi { + /** @see ExceededQueueQuota */ + scheduled: ExceededQueueQuotaFromApi; + /** @see ExceededQueueQuota */ + regular: ExceededQueueQuotaFromApi; +} diff --git a/packages/mailgun/src/models/v1/index.ts b/packages/mailgun/src/models/v1/index.ts new file mode 100644 index 00000000..2ec714f6 --- /dev/null +++ b/packages/mailgun/src/models/v1/index.ts @@ -0,0 +1,13 @@ +export * from './emails/request/override-properties/override-properties'; +export * from './emails/request/send-email-request/send-email-request'; +export * from './emails/request/send-email-request-base/send-email-request-base'; +export * from './emails/request/send-mime-email-request/send-mime-email-request'; +export * from './emails/request/template-properties/template-properties'; +export * from './emails/response/bad-request/bad-request'; +export * from './emails/response/email-not-found/email-not-found'; +export * from './emails/response/exceeded-queue-quota/exceeded-queue-quota'; +export * from './emails/response/generic-response/generic-response'; +export * from './emails/response/get-email-response/get-email-response'; +export * from './emails/response/queue-status-disabled-details/queue-status-disabled-details'; +export * from './emails/response/send-email-response/send-email-response'; +export * from './emails/response/sending-queues-status-response/sending-queues-status-response'; diff --git a/packages/mailgun/src/rest/index.ts b/packages/mailgun/src/rest/index.ts new file mode 100644 index 00000000..5b98253d --- /dev/null +++ b/packages/mailgun/src/rest/index.ts @@ -0,0 +1 @@ +export * from './v1'; diff --git a/packages/mailgun/src/rest/v1/emails/emails-api.jest.fixture.ts b/packages/mailgun/src/rest/v1/emails/emails-api.jest.fixture.ts new file mode 100644 index 00000000..b8d78d5d --- /dev/null +++ b/packages/mailgun/src/rest/v1/emails/emails-api.jest.fixture.ts @@ -0,0 +1,32 @@ +import { EmailsApi } from './emails-api'; +import { + GenericResponse, + GetEmailResponse, + SendEmailResponse, + SendingQueuesStatusResponse, + SendEmailRequest, + SendMimeEmailRequest, +} from '../../../models'; + +export class EmailsApiFixture implements Partial> { + /** + * Fixture associated to function sendEmail + */ + public sendEmail: jest.Mock, [string, SendEmailRequest]> = jest.fn(); + /** + * Fixture associated to function sendMimeEmail + */ + public sendMimeEmail: jest.Mock, [string, SendMimeEmailRequest]> = jest.fn(); + /** + * Fixture associated to function getEmail + */ + public getEmail: jest.Mock, [string, string]> = jest.fn(); + /** + * Fixture associated to function purgeDomainQueues + */ + public purgeDomainQueues: jest.Mock, [string]> = jest.fn(); + /** + * Fixture associated to function getSendingQueuesStatus + */ + public getSendingQueuesStatus: jest.Mock, [string]> = jest.fn(); +} diff --git a/packages/mailgun/src/rest/v1/emails/emails-api.ts b/packages/mailgun/src/rest/v1/emails/emails-api.ts new file mode 100644 index 00000000..f0de2f07 --- /dev/null +++ b/packages/mailgun/src/rest/v1/emails/emails-api.ts @@ -0,0 +1,214 @@ +import { + GenericResponse, + GenericResponseFromApi, + GetEmailResponse, + GetEmailResponseFromApi, + SendEmailRequest, + SendEmailResponse, + SendEmailResponseFromApi, + SendingQueuesStatusResponse, + SendingQueuesStatusResponseFromApi, + SendMimeEmailRequest, + transformGenericResponseIntoClientResponse, + transformGetEmailResponseIntoClientResponse, + transformSendEmailRequestIntoApiRequestBody, + transformSendEmailResponseIntoClientResponse, + transformSendingQueuesStatusResponseIntoClientResponse, + transformSendMimeEmailRequestIntoApiRequestBody, +} from '../../../models'; +import { RequestBody, SinchClientParameters, MAILGUN_STORAGE_HOSTNAMES } from '@sinch/sdk-client'; +import { MailgunDomainApi } from '../mailgun-domain-api'; + +export class EmailsApi extends MailgunDomainApi { + storageHostnames: string[]; + + /** + * Initialize your interface + * + * @param {SinchClientParameters} sinchClientParameters - The parameters used to initialize the API Client. + */ + constructor(sinchClientParameters: SinchClientParameters) { + super(sinchClientParameters, 'EmailsApi'); + this.storageHostnames = MAILGUN_STORAGE_HOSTNAMES; + } + + /** + * Send an email + * Pass the components of the messages such as To, From, Subject, HTML and text parts, attachments, etc. Mailgun will build a MIME representation of the message and send it. Note: In order to send you must provide one of the following parameters: 'text', 'html', 'amp-html' or 'template' + * @param { string } domainName - Domain name used to send the message + * @param { SendEmailRequest } request - The data to provide to the API call. + */ + public async sendEmail(domainName: string, request: SendEmailRequest): Promise { + this.client = this.getSinchClient(); + const getParams = this.client.extractQueryParams(request, [] as never[]); + const headers: { [key: string]: string | undefined } = { + Accept: 'application/json', + }; + const body: RequestBody = transformSendEmailRequestIntoApiRequestBody(request); + const basePathUrl = `${this.client.apiClientOptions.hostname}/v3/${domainName}/messages`; + + const requestOptions = await this.client.prepareOptions(basePathUrl, 'POST', getParams, headers, body || undefined); + const url = this.client.prepareUrl(requestOptions.hostname, requestOptions.queryParams); + + const apiResponse = await this.client.processCall({ + url, + requestOptions, + apiName: this.apiName, + operationId: 'sendEmail', + }); + + return transformSendEmailResponseIntoClientResponse(apiResponse); + } + + /** + * Send an email in MIME format + * Build a MIME string yourself using a MIME library for your programming language and submit it to Mailgun. + * @param { string } domainName - Domain name used to send the message + * @param { SendMimeEmailRequest } request - The data to provide to the API call. + */ + public async sendMimeEmail(domainName: string, request: SendMimeEmailRequest): Promise { + this.client = this.getSinchClient(); + const getParams = this.client.extractQueryParams(request, [] as never[]); + const headers: { [key: string]: string | undefined } = { + Accept: 'application/json', + }; + const body: RequestBody = transformSendMimeEmailRequestIntoApiRequestBody(request); + const basePathUrl = `${this.client.apiClientOptions.hostname}/v3/${domainName}/messages.mime`; + + const requestOptions = await this.client.prepareOptions(basePathUrl, 'POST', getParams, headers, body || undefined); + const url = this.client.prepareUrl(requestOptions.hostname, requestOptions.queryParams); + + const apiResponse = await this.client.processCall({ + url, + requestOptions, + apiName: this.apiName, + operationId: 'sendMimeEmail', + }); + + return transformSendEmailResponseIntoClientResponse(apiResponse); + } + + /** + * Retrieve a stored email + * Event(s) created from sending an email with Mailgun will contain a `storage.key` to use to retrieve the email. + * @param { string } domainName - Domain name that was used to send the email + * @param { string } storageKey - Storage key from the emails associated events + */ + public async getEmail(domainName: string, storageKey: string): Promise { + this.client = this.getSinchClient(); + const getParams = {}; + const headers: { [key: string]: string | undefined } = { + 'Content-Type': 'application/json', + Accept: 'application/json', + }; + const body: RequestBody = ''; + const basePathUrl = `${this.client.apiClientOptions.hostname}/v3/domains/${domainName}/messages/${storageKey}`; + + const requestOptions = await this.client.prepareOptions(basePathUrl, 'GET', getParams, headers, body || undefined); + const url = this.client.prepareUrl(requestOptions.hostname, requestOptions.queryParams); + + const apiResponse = await this.client.processCall({ + url, + requestOptions, + apiName: this.apiName, + operationId: 'getEmail', + }); + + return transformGetEmailResponseIntoClientResponse(apiResponse); + } + + /** + * Delete scheduled and undelivered mail + * Deletes all scheduled and undelivered mail from the domain queue. This endpoint must be called on the storage API host and in the domain's region. e.g. https://storage-us-east4.api.mailgun.net/v3/example.com/envelopes + * + * The storage hosts are `storage-us-east4.api.mailgun.net`, `storage-us-west1.api.mailgun.net`, and `storage-europe-west1.api.mailgun.net`. + * @param { string } domainName - The name of the domain you want to delete envelope from + */ + public async purgeDomainQueues(domainName: string): Promise { + const requests = this.storageHostnames.map((hostname) => + this.purgeStorageQueue(hostname, domainName) + .then((response) => { + return { hostname, response }; + }) + .catch(() => { + console.log(`Request failed at: ${hostname}`); + return null; + }), + ); + + const results = await Promise.allSettled(requests); + + const successfulResponses = results + .filter((result) => result.status === 'fulfilled' && result.value) + .map((result) => (result as PromiseFulfilledResult<{ hostname: string; response: GenericResponse }>).value); + + if (successfulResponses.length > 0) { + successfulResponses.forEach(({ hostname }) => console.log(`Domain queue successfully purged at: ${hostname}`)); + return successfulResponses[0].response; + } else { + throw new Error('All requests failed. Domain may not exist in any region.'); + } + } + + public async purgeStorageQueue(storageHostname: string, domainName: string): Promise { + this.client = this.getSinchClient(); + const getParams = {}; + const headers: { [key: string]: string | undefined } = { + 'Content-Type': 'application/json', + Accept: 'application/json', + }; + const body: RequestBody = ''; + const basePathUrl = `${storageHostname}/v3/${domainName}/envelopes`; + + const requestOptions = await this.client.prepareOptions( + basePathUrl, + 'DELETE', + getParams, + headers, + body || undefined, + ); + const url = this.client.prepareUrl(requestOptions.hostname, requestOptions.queryParams); + + const apiResponse = await this.client.processCall({ + url, + requestOptions, + apiName: this.apiName, + operationId: 'purgeDomainQueues', + }); + + return transformGenericResponseIntoClientResponse(apiResponse); + } + + /** + * Get messages queue status + * Provides default and scheduled message queue information. + * @param { string } name - The name of the domain you want get sending queues from + */ + public async getSendingQueuesStatus(name: string): Promise { + this.client = this.getSinchClient(); + const getParams = {}; + const headers: { [key: string]: string | undefined } = { + 'Content-Type': 'application/json', + Accept: 'application/json', + }; + const body: RequestBody = ''; + const basePathUrl = `${this.client.apiClientOptions.hostname}/v3/domains/${name}/sending_queues`; + + const requestOptions = await this.client.prepareOptions(basePathUrl, 'GET', getParams, headers, body || undefined); + const url = this.client.prepareUrl(requestOptions.hostname, requestOptions.queryParams); + + const apiResponse = await this.client.processCall({ + url, + requestOptions, + apiName: this.apiName, + operationId: 'getSendingQueuesStatus', + }); + + return transformSendingQueuesStatusResponseIntoClientResponse(apiResponse); + } + + public setStorageHostnames(storageHostnames: string[]) { + this.storageHostnames = storageHostnames; + } + +} diff --git a/packages/mailgun/src/rest/v1/emails/index.ts b/packages/mailgun/src/rest/v1/emails/index.ts new file mode 100644 index 00000000..19072ef6 --- /dev/null +++ b/packages/mailgun/src/rest/v1/emails/index.ts @@ -0,0 +1,2 @@ +export * from './emails-api'; +export * from './emails-api.jest.fixture'; diff --git a/packages/mailgun/src/rest/v1/index.ts b/packages/mailgun/src/rest/v1/index.ts new file mode 100644 index 00000000..0c9c642d --- /dev/null +++ b/packages/mailgun/src/rest/v1/index.ts @@ -0,0 +1,2 @@ +export * from './emails'; +export * from './mailgun-service'; diff --git a/packages/mailgun/src/rest/v1/mailgun-domain-api.ts b/packages/mailgun/src/rest/v1/mailgun-domain-api.ts new file mode 100644 index 00000000..f05f5f71 --- /dev/null +++ b/packages/mailgun/src/rest/v1/mailgun-domain-api.ts @@ -0,0 +1,97 @@ +import { + Api, + ApiClient, + ApiFetchClient, + SinchClientParameters, + buildMailgunApiClientOptions, + MAILGUN_HOSTNAME, + formatRegionalizedHostname, + MailgunRegion, + SupportedMailgunRegion, + MailgunCredentials, +} from '@sinch/sdk-client'; + +export class MailgunDomainApi implements Api { + public readonly apiName: string; + public client?: ApiClient; + private sinchClientParameters: SinchClientParameters; + + constructor(sinchClientParameters: SinchClientParameters, apiName: string) { + this.sinchClientParameters = sinchClientParameters; + this.apiName = apiName; + } + + /** + * Update the default hostname for the API + * @param {string} hostname - The new hostname to use for the APIs. + */ + public setHostname(hostname: string) { + try { + this.client = this.getSinchClient(); + this.client.apiClientOptions.hostname = hostname; + } catch (error) { + console.error('Impossible to set a new hostname, the credentials need to be provided first.'); + throw error; + } + } + + /** + * Update the region in the hostname + * @param {MailgunRegion} region - The new region to send the requests to + */ + public setRegion(region: MailgunRegion) { + this.sinchClientParameters.mailgunRegion = region; + if (this.client) { + this.client.apiClientOptions.hostname = this.buildHostname(region); + } + } + + /** + * Updates the credentials used to authenticate API requests + * @param {MailgunCredentials} credentials + */ + public setCredentials(credentials: MailgunCredentials) { + const parametersBackup = { ...this.sinchClientParameters }; + this.sinchClientParameters = { + ...parametersBackup, + ...credentials, + }; + this.resetApiClient(); + try { + this.getSinchClient(); + } catch (error) { + console.error('Impossible to assign the new credentials to the Mailgun API'); + this.sinchClientParameters = parametersBackup; + throw error; + } + } + + private resetApiClient() { + this.client = undefined; + } + + /** + * Checks the configuration parameters are ok and initialize the API client. Once initialized, the same instance will + * be returned for the subsequent API calls (singleton pattern) + * @return {ApiClient} the API Client or throws an error in case the configuration parameters are not ok + * @private + */ + public getSinchClient(): ApiClient { + if (!this.client) { + const region = this.sinchClientParameters.mailgunRegion ?? MailgunRegion.DEFAULT; + if(!Object.values(SupportedMailgunRegion).includes(region as SupportedMailgunRegion)) { + console.warn(`The region "${region}" is not known as a supported region for the Mailgun API`); + } + const apiClientOptions = buildMailgunApiClientOptions(this.sinchClientParameters); + this.client = new ApiFetchClient(apiClientOptions); + this.client.apiClientOptions.hostname = this.buildHostname(region); + } + return this.client; + } + + private buildHostname(region: MailgunRegion) { + const formattedRegion = region !== '' ? `${region}.` : ''; + return this.sinchClientParameters.mailgunHostname + ?? formatRegionalizedHostname(MAILGUN_HOSTNAME, formattedRegion); + } +} diff --git a/packages/mailgun/src/rest/v1/mailgun-service.ts b/packages/mailgun/src/rest/v1/mailgun-service.ts new file mode 100644 index 00000000..7c03579f --- /dev/null +++ b/packages/mailgun/src/rest/v1/mailgun-service.ts @@ -0,0 +1,26 @@ +import { SinchClientParameters } from '@sinch/sdk-client'; +import { EmailsApi } from './emails'; + +export class MailgunService { + public readonly emails: EmailsApi; + + constructor(params: SinchClientParameters) { + this.emails = new EmailsApi(params); + } + + /** + * Update the default hostname for each API + * @param {string} hostname - The new hostname to use for all the APIs. + */ + public setHostname(hostname: string) { + this.emails.setHostname(hostname); + } + + /** + * Update the default hostname to purge the emails queues + * @param {string} storageHostnames - The new hostnames for the emails storage. + */ + public setStorageHostnames(storageHostnames: string[]) { + this.emails.setStorageHostnames(storageHostnames); + } +} diff --git a/packages/mailgun/tests/models/v1/emails/request/index.ts b/packages/mailgun/tests/models/v1/emails/request/index.ts new file mode 100644 index 00000000..03a610c5 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/request/index.ts @@ -0,0 +1,2 @@ +export * from './send-email-request.models'; +export * from './send-mime-email-request.models'; diff --git a/packages/mailgun/tests/models/v1/emails/request/send-email-request.models.ts b/packages/mailgun/tests/models/v1/emails/request/send-email-request.models.ts new file mode 100644 index 00000000..12c70f0c --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/request/send-email-request.models.ts @@ -0,0 +1,76 @@ +import { Mailgun } from '../../../../../src'; + +export const sendEmailRequestWithHtml: Mailgun.SendEmailRequest = { + from: 'from value', + to: 'to value', + cc: 'cc value', + bcc: 'bcc value', + subject: 'subject value', + text: 'text value', + html: 'html value', + amp_html: 'amp_html value', + attachment: 'attachment value', + inline: 'inline value', + overrideProperties: { + tag: 'tag value', + sendingIp: 'sendingIp value', + sendingIpPool: 'sendingIpPool value', + deliveryTime: 'deliveryTime value', + deliveryTimeOptimizePeriod: 'deliveryTimeOptimizePeriod value', + enableDkimSignature: 'enableDkimSignature value', + secondaryDkim: 'secondaryDkim value', + secondaryDkimPublic: 'secondaryDkim value', + requireTls: 'requireTls value', + skipVerification: 'skipVerification value', + timeZoneLocalize: 'timeZoneLocalize value', + tracking: 'tracking value', + trackingClicks: 'trackingClicks value', + trackingOpens: 'trackingOpens value', + trackingPixelLocationTop: 'trackingPixelLocationTop value', + isTestMode: false, + }, + 'h:X-Mailgun-Sending-Ip-Pool': 'xx.xx.xxx.x', + 'v:first_name': 'John', + 'v:last_name': 'Smith', + 'v:my_message_id': 123, +}; + +export const sendEmailRequestWithTemplate: Mailgun.SendEmailRequest = { + from: 'from value', + to: 'to value', + cc: 'cc value', + bcc: 'bcc value', + subject: 'subject value', + text: 'text value', + template: 'template value', + amp_html: 'amp_html value', + attachment: 'attachment value', + inline: 'inline value', + templateProperties: { + version: 'version value', + text: 'text value', + variables: 'variables value', + }, + overrideProperties: { + tag: 'tag value', + sendingIp: 'sendingIp value', + sendingIpPool: 'sendingIpPool value', + deliveryTime: 'deliveryTime value', + deliveryTimeOptimizePeriod: 'deliveryTimeOptimizePeriod value', + enableDkimSignature: 'enableDkimSignature value', + secondaryDkim: 'secondaryDkim value', + secondaryDkimPublic: 'secondaryDkim value', + requireTls: 'requireTls value', + skipVerification: 'skipVerification value', + timeZoneLocalize: 'timeZoneLocalize value', + tracking: 'tracking value', + trackingClicks: 'trackingClicks value', + trackingOpens: 'trackingOpens value', + trackingPixelLocationTop: 'trackingPixelLocationTop value', + isTestMode: false, + }, + 'h:X-Mailgun-Sending-Ip-Pool': 'xx.xx.xxx.x', + 'v:first_name': 'John', + 'v:last_name': 'Smith', + 'v:my_message_id': 123, +}; diff --git a/packages/mailgun/tests/models/v1/emails/request/send-email-request.test.ts b/packages/mailgun/tests/models/v1/emails/request/send-email-request.test.ts new file mode 100644 index 00000000..708673c9 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/request/send-email-request.test.ts @@ -0,0 +1,153 @@ +import FormData = require('form-data'); +import { transformSendEmailRequestIntoApiRequestBody } from '../../../../../src/models'; +import { sendEmailRequestWithHtml, sendEmailRequestWithTemplate } from './send-email-request.models'; + +describe('SendEmailRequest', () => { + + let appendSpy: any; + + beforeEach(() => { + appendSpy = jest.spyOn(FormData.prototype, 'append'); + }); + + afterEach(() => { + appendSpy.mockRestore(); + }); + + it('should transform a client object using HTML into an API object', () => { + transformSendEmailRequestIntoApiRequestBody(sendEmailRequestWithHtml); + expect(appendSpy).toHaveBeenCalledTimes(30); + expect(appendSpy).toHaveBeenCalledWith('from', + sendEmailRequestWithHtml.from); + expect(appendSpy).toHaveBeenCalledWith('to', + sendEmailRequestWithHtml.to); + expect(appendSpy).toHaveBeenCalledWith('cc', + sendEmailRequestWithHtml.cc); + expect(appendSpy).toHaveBeenCalledWith('bcc', + sendEmailRequestWithHtml.bcc); + expect(appendSpy).toHaveBeenCalledWith('subject', + sendEmailRequestWithHtml.subject); + expect(appendSpy).toHaveBeenCalledWith('text', + sendEmailRequestWithHtml.text); + expect(appendSpy).toHaveBeenCalledWith('html', + sendEmailRequestWithHtml.html); + expect(appendSpy).toHaveBeenCalledWith('amp-html', + sendEmailRequestWithHtml.amp_html); + expect(appendSpy).toHaveBeenCalledWith('attachment', + sendEmailRequestWithHtml.attachment); + expect(appendSpy).toHaveBeenCalledWith('inline', + sendEmailRequestWithHtml.inline); + expect(appendSpy).toHaveBeenCalledWith('o:tag', + sendEmailRequestWithHtml.overrideProperties?.tag); + expect(appendSpy).toHaveBeenCalledWith('o:sending-ip', + sendEmailRequestWithHtml.overrideProperties?.sendingIp); + expect(appendSpy).toHaveBeenCalledWith('o:sending-ip-pool', + sendEmailRequestWithHtml.overrideProperties?.sendingIpPool); + expect(appendSpy).toHaveBeenCalledWith('o:deliverytime', + sendEmailRequestWithHtml.overrideProperties?.deliveryTime); + expect(appendSpy).toHaveBeenCalledWith('o:deliverytime-optimize-period', + sendEmailRequestWithHtml.overrideProperties?.deliveryTimeOptimizePeriod); + expect(appendSpy).toHaveBeenCalledWith('o:dkim', + sendEmailRequestWithHtml.overrideProperties?.enableDkimSignature); + expect(appendSpy).toHaveBeenCalledWith('o:secondary-dkim', + sendEmailRequestWithHtml.overrideProperties?.secondaryDkim); + expect(appendSpy).toHaveBeenCalledWith('o:secondary-dkim-public', + sendEmailRequestWithHtml.overrideProperties?.secondaryDkimPublic); + expect(appendSpy).toHaveBeenCalledWith('o:require-tls', + sendEmailRequestWithHtml.overrideProperties?.requireTls); + expect(appendSpy).toHaveBeenCalledWith('o:skip-verification', + sendEmailRequestWithHtml.overrideProperties?.skipVerification); + expect(appendSpy).toHaveBeenCalledWith('o:time-zone-localize', + sendEmailRequestWithHtml.overrideProperties?.timeZoneLocalize); + expect(appendSpy).toHaveBeenCalledWith('o:tracking', + sendEmailRequestWithHtml.overrideProperties?.tracking); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-opens', + sendEmailRequestWithHtml.overrideProperties?.trackingOpens); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-clicks', + sendEmailRequestWithHtml.overrideProperties?.trackingClicks); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-pixel-location-top', + sendEmailRequestWithHtml.overrideProperties?.trackingPixelLocationTop); + expect(appendSpy).toHaveBeenCalledWith('o:testmode', + sendEmailRequestWithHtml.overrideProperties?.isTestMode); + expect(appendSpy).toHaveBeenCalledWith('h:X-Mailgun-Sending-Ip-Pool', + sendEmailRequestWithHtml['h:X-Mailgun-Sending-Ip-Pool']); + expect(appendSpy).toHaveBeenCalledWith('v:first_name', + sendEmailRequestWithHtml['v:first_name']); + expect(appendSpy).toHaveBeenCalledWith('v:last_name', + sendEmailRequestWithHtml['v:last_name']); + expect(appendSpy).toHaveBeenCalledWith('v:my_message_id', + sendEmailRequestWithHtml['v:my_message_id']); + }); + + it('should transform a client object using a template into an API object', () => { + transformSendEmailRequestIntoApiRequestBody(sendEmailRequestWithTemplate); + expect(appendSpy).toHaveBeenCalledTimes(33); + expect(appendSpy).toHaveBeenCalledWith('from', + sendEmailRequestWithTemplate.from); + expect(appendSpy).toHaveBeenCalledWith('to', + sendEmailRequestWithTemplate.to); + expect(appendSpy).toHaveBeenCalledWith('cc', + sendEmailRequestWithTemplate.cc); + expect(appendSpy).toHaveBeenCalledWith('bcc', + sendEmailRequestWithTemplate.bcc); + expect(appendSpy).toHaveBeenCalledWith('subject', + sendEmailRequestWithTemplate.subject); + expect(appendSpy).toHaveBeenCalledWith('text', + sendEmailRequestWithTemplate.text); + expect(appendSpy).toHaveBeenCalledWith('template', + sendEmailRequestWithTemplate.template); + expect(appendSpy).toHaveBeenCalledWith('amp-html', + sendEmailRequestWithTemplate.amp_html); + expect(appendSpy).toHaveBeenCalledWith('attachment', + sendEmailRequestWithTemplate.attachment); + expect(appendSpy).toHaveBeenCalledWith('inline', + sendEmailRequestWithTemplate.inline); + expect(appendSpy).toHaveBeenCalledWith('o:tag', + sendEmailRequestWithTemplate.overrideProperties?.tag); + expect(appendSpy).toHaveBeenCalledWith('o:sending-ip', + sendEmailRequestWithTemplate.overrideProperties?.sendingIp); + expect(appendSpy).toHaveBeenCalledWith('o:sending-ip-pool', + sendEmailRequestWithTemplate.overrideProperties?.sendingIpPool); + expect(appendSpy).toHaveBeenCalledWith('o:deliverytime', + sendEmailRequestWithTemplate.overrideProperties?.deliveryTime); + expect(appendSpy).toHaveBeenCalledWith('o:deliverytime-optimize-period', + sendEmailRequestWithTemplate.overrideProperties?.deliveryTimeOptimizePeriod); + expect(appendSpy).toHaveBeenCalledWith('o:dkim', + sendEmailRequestWithTemplate.overrideProperties?.enableDkimSignature); + expect(appendSpy).toHaveBeenCalledWith('o:secondary-dkim', + sendEmailRequestWithTemplate.overrideProperties?.secondaryDkim); + expect(appendSpy).toHaveBeenCalledWith('o:secondary-dkim-public', + sendEmailRequestWithTemplate.overrideProperties?.secondaryDkimPublic); + expect(appendSpy).toHaveBeenCalledWith('o:require-tls', + sendEmailRequestWithTemplate.overrideProperties?.requireTls); + expect(appendSpy).toHaveBeenCalledWith('o:skip-verification', + sendEmailRequestWithTemplate.overrideProperties?.skipVerification); + expect(appendSpy).toHaveBeenCalledWith('o:time-zone-localize', + sendEmailRequestWithTemplate.overrideProperties?.timeZoneLocalize); + expect(appendSpy).toHaveBeenCalledWith('o:tracking', + sendEmailRequestWithTemplate.overrideProperties?.tracking); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-opens', + sendEmailRequestWithTemplate.overrideProperties?.trackingOpens); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-clicks', + sendEmailRequestWithTemplate.overrideProperties?.trackingClicks); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-pixel-location-top', + sendEmailRequestWithTemplate.overrideProperties?.trackingPixelLocationTop); + expect(appendSpy).toHaveBeenCalledWith('o:testmode', + sendEmailRequestWithTemplate.overrideProperties?.isTestMode); + expect(appendSpy).toHaveBeenCalledWith('t:text', + sendEmailRequestWithTemplate.templateProperties?.text); + expect(appendSpy).toHaveBeenCalledWith('t:version', + sendEmailRequestWithTemplate.templateProperties?.version); + expect(appendSpy).toHaveBeenCalledWith('t:variables', + sendEmailRequestWithTemplate.templateProperties?.variables); + expect(appendSpy).toHaveBeenCalledWith('h:X-Mailgun-Sending-Ip-Pool', + sendEmailRequestWithTemplate['h:X-Mailgun-Sending-Ip-Pool']); + expect(appendSpy).toHaveBeenCalledWith('v:first_name', + sendEmailRequestWithTemplate['v:first_name']); + expect(appendSpy).toHaveBeenCalledWith('v:last_name', + sendEmailRequestWithTemplate['v:last_name']); + expect(appendSpy).toHaveBeenCalledWith('v:my_message_id', + sendEmailRequestWithTemplate['v:my_message_id']); + }); + +}); diff --git a/packages/mailgun/tests/models/v1/emails/request/send-mime-email-request.models.ts b/packages/mailgun/tests/models/v1/emails/request/send-mime-email-request.models.ts new file mode 100644 index 00000000..345d5302 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/request/send-mime-email-request.models.ts @@ -0,0 +1,34 @@ +import { Mailgun } from '../../../../../src'; + +export const sendMimeEmailRequest: Mailgun.SendMimeEmailRequest = { + to: 'to value', + message: 'message value', + template: 'template value', + templateProperties: { + version: 'version value', + text: 'text value', + variables: 'variables value', + }, + overrideProperties: { + tag: 'tag value', + sendingIp: 'sendingIp value', + sendingIpPool: 'sendingIpPool value', + deliveryTime: 'deliveryTime value', + deliveryTimeOptimizePeriod: 'deliveryTimeOptimizePeriod value', + enableDkimSignature: 'enableDkimSignature value', + secondaryDkim: 'secondaryDkim value', + secondaryDkimPublic: 'secondaryDkim value', + requireTls: 'requireTls value', + skipVerification: 'skipVerification value', + timeZoneLocalize: 'timeZoneLocalize value', + tracking: 'tracking value', + trackingClicks: 'trackingClicks value', + trackingOpens: 'trackingOpens value', + trackingPixelLocationTop: 'trackingPixelLocationTop value', + isTestMode: false, + }, + 'h:X-Mailgun-Sending-Ip-Pool': 'xx.xx.xxx.x', + 'v:first_name': 'John', + 'v:last_name': 'Smith', + 'v:my_message_id': 123, +}; diff --git a/packages/mailgun/tests/models/v1/emails/request/send-mime-email-request.test.ts b/packages/mailgun/tests/models/v1/emails/request/send-mime-email-request.test.ts new file mode 100644 index 00000000..12b57af5 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/request/send-mime-email-request.test.ts @@ -0,0 +1,63 @@ +import FormData = require('form-data'); +import { transformSendMimeEmailRequestIntoApiRequestBody } from '../../../../../src/models'; +import { sendMimeEmailRequest } from './send-mime-email-request.models'; + +describe('SendMimeEmailRequest', () => { + + it('should transform a client object using a template into an API object', () => { + const appendSpy = jest.spyOn(FormData.prototype, 'append'); + transformSendMimeEmailRequestIntoApiRequestBody(sendMimeEmailRequest); + expect(appendSpy).toHaveBeenCalledTimes(26); + expect(appendSpy).toHaveBeenCalledWith('to', + sendMimeEmailRequest.to); + expect(appendSpy).toHaveBeenCalledWith('template', + sendMimeEmailRequest.template); + expect(appendSpy).toHaveBeenCalledWith('o:tag', + sendMimeEmailRequest.overrideProperties?.tag); + expect(appendSpy).toHaveBeenCalledWith('o:sending-ip', + sendMimeEmailRequest.overrideProperties?.sendingIp); + expect(appendSpy).toHaveBeenCalledWith('o:sending-ip-pool', + sendMimeEmailRequest.overrideProperties?.sendingIpPool); + expect(appendSpy).toHaveBeenCalledWith('o:deliverytime', + sendMimeEmailRequest.overrideProperties?.deliveryTime); + expect(appendSpy).toHaveBeenCalledWith('o:deliverytime-optimize-period', + sendMimeEmailRequest.overrideProperties?.deliveryTimeOptimizePeriod); + expect(appendSpy).toHaveBeenCalledWith('o:dkim', + sendMimeEmailRequest.overrideProperties?.enableDkimSignature); + expect(appendSpy).toHaveBeenCalledWith('o:secondary-dkim', + sendMimeEmailRequest.overrideProperties?.secondaryDkim); + expect(appendSpy).toHaveBeenCalledWith('o:secondary-dkim-public', + sendMimeEmailRequest.overrideProperties?.secondaryDkimPublic); + expect(appendSpy).toHaveBeenCalledWith('o:require-tls', + sendMimeEmailRequest.overrideProperties?.requireTls); + expect(appendSpy).toHaveBeenCalledWith('o:skip-verification', + sendMimeEmailRequest.overrideProperties?.skipVerification); + expect(appendSpy).toHaveBeenCalledWith('o:time-zone-localize', + sendMimeEmailRequest.overrideProperties?.timeZoneLocalize); + expect(appendSpy).toHaveBeenCalledWith('o:tracking', + sendMimeEmailRequest.overrideProperties?.tracking); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-opens', + sendMimeEmailRequest.overrideProperties?.trackingOpens); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-clicks', + sendMimeEmailRequest.overrideProperties?.trackingClicks); + expect(appendSpy).toHaveBeenCalledWith('o:tracking-pixel-location-top', + sendMimeEmailRequest.overrideProperties?.trackingPixelLocationTop); + expect(appendSpy).toHaveBeenCalledWith('o:testmode', + sendMimeEmailRequest.overrideProperties?.isTestMode); + expect(appendSpy).toHaveBeenCalledWith('t:text', + sendMimeEmailRequest.templateProperties?.text); + expect(appendSpy).toHaveBeenCalledWith('t:version', + sendMimeEmailRequest.templateProperties?.version); + expect(appendSpy).toHaveBeenCalledWith('t:variables', + sendMimeEmailRequest.templateProperties?.variables); + expect(appendSpy).toHaveBeenCalledWith('h:X-Mailgun-Sending-Ip-Pool', + sendMimeEmailRequest['h:X-Mailgun-Sending-Ip-Pool']); + expect(appendSpy).toHaveBeenCalledWith('v:first_name', + sendMimeEmailRequest['v:first_name']); + expect(appendSpy).toHaveBeenCalledWith('v:last_name', + sendMimeEmailRequest['v:last_name']); + expect(appendSpy).toHaveBeenCalledWith('v:my_message_id', + sendMimeEmailRequest['v:my_message_id']); + }); + +}); diff --git a/packages/mailgun/tests/models/v1/emails/response/bad-request.models.ts b/packages/mailgun/tests/models/v1/emails/response/bad-request.models.ts new file mode 100644 index 00000000..ec9a2d77 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/bad-request.models.ts @@ -0,0 +1,9 @@ +import { Mailgun } from '../../../../../src'; + +export const badRequestFromApi: Mailgun.BadRequestFromApi = { + message: 'Invalid storage key', +}; + +export const badRequest: Mailgun.BadRequest = { + message: 'Invalid storage key', +}; diff --git a/packages/mailgun/tests/models/v1/emails/response/bad-request.test.ts b/packages/mailgun/tests/models/v1/emails/response/bad-request.test.ts new file mode 100644 index 00000000..8c9733e8 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/bad-request.test.ts @@ -0,0 +1,13 @@ +import { + transformBadRequestIntoClientResponse, +} from '../../../../../src/models'; +import { badRequest, badRequestFromApi } from './bad-request.models'; + +describe('BadRequest', () => { + + it('should convert an API object into a client object', () => { + const transformedResponse = transformBadRequestIntoClientResponse(badRequestFromApi); + expect(transformedResponse).toEqual(badRequest); + }); + +}); diff --git a/packages/mailgun/tests/models/v1/emails/response/email-not-found.models.ts b/packages/mailgun/tests/models/v1/emails/response/email-not-found.models.ts new file mode 100644 index 00000000..5b188ede --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/email-not-found.models.ts @@ -0,0 +1,9 @@ +import { Mailgun } from '../../../../../src'; + +export const emailNotFoundFromApi: Mailgun.EmailNotFoundFromApi = { + message: 'Message not found', +}; + +export const emailNotFound: Mailgun.EmailNotFound = { + message: 'Message not found', +}; diff --git a/packages/mailgun/tests/models/v1/emails/response/email-not-found.test.ts b/packages/mailgun/tests/models/v1/emails/response/email-not-found.test.ts new file mode 100644 index 00000000..1ecb09cb --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/email-not-found.test.ts @@ -0,0 +1,13 @@ +import { + transformEmailNotFoundIntoClientResponse, +} from '../../../../../src/models'; +import { emailNotFound, emailNotFoundFromApi } from './email-not-found.models'; + +describe('EmailNotFound', () => { + + it('should convert an API object into a client object', () => { + const transformedResponse = transformEmailNotFoundIntoClientResponse(emailNotFoundFromApi); + expect(transformedResponse).toEqual(emailNotFound); + }); + +}); diff --git a/packages/mailgun/tests/models/v1/emails/response/generic-response.models.ts b/packages/mailgun/tests/models/v1/emails/response/generic-response.models.ts new file mode 100644 index 00000000..e6f14583 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/generic-response.models.ts @@ -0,0 +1,9 @@ +import { Mailgun } from '../../../../../src'; + +export const genericResponseFromApi: Mailgun.GenericResponseFromApi = { + message: 'message value', +}; + +export const genericResponse: Mailgun.GenericResponse = { + message: 'message value', +}; diff --git a/packages/mailgun/tests/models/v1/emails/response/generic-response.test.ts b/packages/mailgun/tests/models/v1/emails/response/generic-response.test.ts new file mode 100644 index 00000000..dd639681 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/generic-response.test.ts @@ -0,0 +1,13 @@ +import { + transformGenericResponseIntoClientResponse, +} from '../../../../../src/models'; +import { genericResponse, genericResponseFromApi } from './generic-response.models'; + +describe('GenericResponse', () => { + + it('should convert an API object into a client object', () => { + const transformedResponse = transformGenericResponseIntoClientResponse(genericResponseFromApi); + expect(transformedResponse).toEqual(genericResponse); + }); + +}); diff --git a/packages/mailgun/tests/models/v1/emails/response/get-email-response.models.ts b/packages/mailgun/tests/models/v1/emails/response/get-email-response.models.ts new file mode 100644 index 00000000..70076234 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/get-email-response.models.ts @@ -0,0 +1,49 @@ +import { Mailgun } from '../../../../../src'; + +export const getEmailResponseFromApi: Mailgun.GetEmailResponseFromApi = { + 'X-Mailgun-Deliver-By': new Date('Wed, 06 Jun 2024 07:40:00 +0000'), + subject: '"Mailgun is awesome"', + Subject: '"Mailgun is awesome"', + 'stripped-html': 'This is some html', + 'Content-Type': 'text/html; charset=ascii', + 'stripped-text': 'This is some html', + 'stripped-signature': 'This is a signature', + from: 'foo.bar@my-domain.com', + From: 'foo.bar@my-domain.com', + 'body-plain': 'This is some html', + 'Content-Transfer-Encoding': '7bit', + 'message-headers': [ + ['Mime-Version', '1.0'], + ['Subject', '"Mailgun is awesome"'], + ['From', 'foo.bar@my-domain.com'], + ['To', 'cool.barr@cool.com, bar.baz@gmail.com'], + ['X-Mailgun-Deliver-By', 'Wed, 06 Jun 2024 07:40:00 +0000'], + ['Message-Id', ''], + ['Content-Transfer-Encoding', '7bit'], + ['Content-Type', 'text/html; charset=ascii'], + ], + recipients: 'cool.barr@cool.com, bar.baz@gmail.com', + To: 'cool.barr@cool.com, bar.baz@gmail.com', + sender: 'foo.bar@my-domain.com', + 'body-html': 'This is some html', + 'Message-Id': '', + 'Mime-Version': '1.0', +}; + +export const getEmailResponse: Mailgun.GetEmailResponse = { + contentTransferEncoding: '7bit', + contentType: 'text/html; charset=ascii', + messageId: '', + mimeVersion: '1.0', + to: 'cool.barr@cool.com, bar.baz@gmail.com', + deliveryTime: new Date('Wed, 06 Jun 2024 07:40:00 +0000'), + sender: 'foo.bar@my-domain.com', + recipients: 'cool.barr@cool.com, bar.baz@gmail.com', + from: 'foo.bar@my-domain.com', + subject: '"Mailgun is awesome"', + bodyHtml: 'This is some html', + bodyPlain: 'This is some html', + strippedHtml: 'This is some html', + strippedText: 'This is some html', + strippedSignature: 'This is a signature', +}; diff --git a/packages/mailgun/tests/models/v1/emails/response/get-email-response.test.ts b/packages/mailgun/tests/models/v1/emails/response/get-email-response.test.ts new file mode 100644 index 00000000..8daed6a2 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/get-email-response.test.ts @@ -0,0 +1,13 @@ +import { + transformGetEmailResponseIntoClientResponse, +} from '../../../../../src/models'; +import { getEmailResponse, getEmailResponseFromApi } from './get-email-response.models'; + +describe('GetEmailResponse', () => { + + it('should convert an API object into a client object', () => { + const transformedResponse = transformGetEmailResponseIntoClientResponse(getEmailResponseFromApi); + expect(transformedResponse).toEqual(getEmailResponse); + }); + +}); diff --git a/packages/mailgun/tests/models/v1/emails/response/index.ts b/packages/mailgun/tests/models/v1/emails/response/index.ts new file mode 100644 index 00000000..98c351ee --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/index.ts @@ -0,0 +1,6 @@ +export * from './bad-request.models'; +export * from './email-not-found.models'; +export * from './generic-response.models'; +export * from './get-email-response.models'; +export * from './send-email-response.models'; +export * from './sending-queues-status-response.models'; diff --git a/packages/mailgun/tests/models/v1/emails/response/send-email-response.models.ts b/packages/mailgun/tests/models/v1/emails/response/send-email-response.models.ts new file mode 100644 index 00000000..2d9cf7c7 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/send-email-response.models.ts @@ -0,0 +1,11 @@ +import { Mailgun } from '../../../../../src'; + +export const sendEmailResponseFromApi: Mailgun.SendEmailResponseFromApi = { + id: 'id value', + message: 'Queued. Thank you.', +}; + +export const sendEmailResponse: Mailgun.SendEmailResponse = { + id: 'id value', + message: 'Queued. Thank you.', +}; diff --git a/packages/mailgun/tests/models/v1/emails/response/send-email-response.test.ts b/packages/mailgun/tests/models/v1/emails/response/send-email-response.test.ts new file mode 100644 index 00000000..ca314a0c --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/send-email-response.test.ts @@ -0,0 +1,15 @@ +import { + transformSendEmailResponseIntoClientResponse, +} from '../../../../../src/models'; +import { sendEmailResponse, sendEmailResponseFromApi } from './send-email-response.models'; + +describe('SendEmailResponse', () => { + + + + it('should convert an API object into a client object', () => { + const transformedResponse = transformSendEmailResponseIntoClientResponse(sendEmailResponseFromApi); + expect(transformedResponse).toEqual(sendEmailResponse); + }); + +}); diff --git a/packages/mailgun/tests/models/v1/emails/response/sending-queues-status-response.models.ts b/packages/mailgun/tests/models/v1/emails/response/sending-queues-status-response.models.ts new file mode 100644 index 00000000..1ca11f00 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/sending-queues-status-response.models.ts @@ -0,0 +1,35 @@ +import { Mailgun } from '../../../../../src'; + +export const sendingQueuesStatusResponseFromApi: Mailgun.SendingQueuesStatusResponseFromApi = { + scheduled: { + is_disabled: false, + disabled: { + reason: 'reason value', + until: 'until value', + }, + }, + regular: { + is_disabled: false, + disabled: { + reason: 'reason value', + until: 'until value', + }, + }, +}; + +export const sendingQueuesStatusResponse: Mailgun.SendingQueuesStatusResponse = { + scheduled: { + isDisabled: false, + disabled: { + reason: 'reason value', + until: 'until value', + }, + }, + regular: { + isDisabled: false, + disabled: { + reason: 'reason value', + until: 'until value', + }, + }, +}; diff --git a/packages/mailgun/tests/models/v1/emails/response/sending-queues-status-response.test.ts b/packages/mailgun/tests/models/v1/emails/response/sending-queues-status-response.test.ts new file mode 100644 index 00000000..bee61aa2 --- /dev/null +++ b/packages/mailgun/tests/models/v1/emails/response/sending-queues-status-response.test.ts @@ -0,0 +1,17 @@ +import { + transformSendingQueuesStatusResponseIntoClientResponse, +} from '../../../../../src/models'; +import { + sendingQueuesStatusResponse, + sendingQueuesStatusResponseFromApi, +} from './sending-queues-status-response.models'; + +describe('SendingQueuesStatusResponse', () => { + + it('should convert an API object into a client object', () => { + const transformedResponse + = transformSendingQueuesStatusResponseIntoClientResponse(sendingQueuesStatusResponseFromApi); + expect(transformedResponse).toEqual(sendingQueuesStatusResponse); + }); + +}); diff --git a/packages/mailgun/tests/rest/v1/emails/emails-api.test.ts b/packages/mailgun/tests/rest/v1/emails/emails-api.test.ts new file mode 100644 index 00000000..96981173 --- /dev/null +++ b/packages/mailgun/tests/rest/v1/emails/emails-api.test.ts @@ -0,0 +1,109 @@ +import { SinchClientParameters } from '@sinch/sdk-client'; +import { EmailsApi, EmailsApiFixture } from '../../../../src'; +import { + sendEmailRequestWithHtml, + sendMimeEmailRequest, +} from '../../../models/v1/emails/request'; +import { + genericResponse, + getEmailResponse, + sendEmailResponse, + sendingQueuesStatusResponse, +} from '../../../models/v1/emails/response'; + +describe('EmailsApi', () => { + let emailsApi: EmailsApi; + let fixture: EmailsApiFixture; + let credentials: SinchClientParameters; + + beforeEach(() => { + fixture = new EmailsApiFixture(); + credentials = { + projectId: 'PROJECT_ID', + keyId: 'KEY_ID', + keySecret: 'KEY_SECRET', + }; + emailsApi = new EmailsApi(credentials); + }); + + describe('sendEmail', () => { + it('should make a POST request to send an email', async () => { + // Given + const domainName: string = 'domainName'; + + // When + fixture.sendEmail.mockResolvedValue(sendEmailResponse); + emailsApi.sendEmail = fixture.sendEmail; + const response = await emailsApi.sendEmail(domainName, sendEmailRequestWithHtml); + + // Then + expect(response).toEqual(sendEmailResponse); + expect(fixture.sendEmail).toHaveBeenCalledWith(domainName, sendEmailRequestWithHtml); + }); + }); + + describe('sendMimeEmail', () => { + it('should make a POST request to send a MIME email', async () => { + // Given + const domainName: string = 'domainName'; + + // When + fixture.sendMimeEmail.mockResolvedValue(sendEmailResponse); + emailsApi.sendMimeEmail = fixture.sendMimeEmail; + const response = await emailsApi.sendMimeEmail(domainName, sendMimeEmailRequest); + + // Then + expect(response).toEqual(sendEmailResponse); + expect(fixture.sendMimeEmail).toHaveBeenCalledWith(domainName, sendMimeEmailRequest); + }); + }); + + describe('getEmail', () => { + it('should make a GET request to retrieve an email', async () => { + // Given + const domainName: string = 'domainName'; + const storageKey: string = 'storageKey'; + + // When + fixture.getEmail.mockResolvedValue(getEmailResponse); + emailsApi.getEmail = fixture.getEmail; + const response = await emailsApi.getEmail(domainName, storageKey); + + // Then + expect(response).toEqual(getEmailResponse); + expect(fixture.getEmail).toHaveBeenCalledWith(domainName, storageKey); + }); + }); + + describe('purgeDomainQueues', () => { + it('should make a DELETE request to purge the domain queues', async () => { + // Given + const domainName: string = 'domainName'; + + // When + fixture.purgeDomainQueues.mockResolvedValue(genericResponse); + emailsApi.purgeDomainQueues = fixture.purgeDomainQueues; + const response = await emailsApi.purgeDomainQueues(domainName); + + // Then + expect(response).toEqual(genericResponse); + expect(fixture.purgeDomainQueues).toHaveBeenCalledWith(domainName); + }); + }); + + describe('getSendingQueuesStatus', () => { + it('should make a GET request to fetch the sending queues status', async () => { + // Given + const name: string = 'name'; + + // When + fixture.getSendingQueuesStatus.mockResolvedValue(sendingQueuesStatusResponse); + emailsApi.getSendingQueuesStatus = fixture.getSendingQueuesStatus; + const response = await emailsApi.getSendingQueuesStatus(name); + + // Then + expect(response).toEqual(sendingQueuesStatusResponse); + expect(fixture.getSendingQueuesStatus).toHaveBeenCalledWith(name); + }); + }); +}); diff --git a/packages/mailgun/tests/rest/v1/emails/emails.steps.ts b/packages/mailgun/tests/rest/v1/emails/emails.steps.ts new file mode 100644 index 00000000..da94b801 --- /dev/null +++ b/packages/mailgun/tests/rest/v1/emails/emails.steps.ts @@ -0,0 +1,112 @@ +import { EmailsApi, MailgunService, Mailgun } from '../../../../src'; +import { Given, When, Then } from '@cucumber/cucumber'; +import * as assert from 'assert'; + +let emailsApi: EmailsApi; +let sendEmailResponse: Mailgun.SendEmailResponse; +let sendMimeEmailResponse: Mailgun.SendEmailResponse; +let getEmailResponse: Mailgun.GetEmailResponse; +let sendingQueuesStatusResponse: Mailgun.SendingQueuesStatusResponse; +let purgeDomainQueuesResponse: Mailgun.GenericResponse; +const domainName = 'sandbox123.mailgun.org'; + +Given('the Mailgun service "Emails" is available', () => { + const mailgunService = new MailgunService({ + mailgunApiKey: 'apiKey', + mailgunHostname: 'http://localhost:3021', + }); + emailsApi = mailgunService.emails; +}); + +When('I send a request to send a text email', async () => { + sendEmailResponse = await emailsApi.sendEmail(domainName, { + from: 'Excited E2E user ', + to: 'destination@e2e.tst', + subject: 'E2E test text email', + text: 'Hello, this is a text message for E2E testing.', + }); +}); + +Then('the sendEmail response contains information about the text email', () => { + assert.equal(sendEmailResponse.id, '<20240606154318.027ac0b5fc80da62@sandbox123.mailgun.org>'); + assert.equal(sendEmailResponse.message, 'Queued. Thank you.'); +}); + +When('I send a request to send a MIME email', async () => { + sendMimeEmailResponse = await emailsApi.sendMimeEmail(domainName, { + to: 'destination@e2e.tst', + message: formatMimeEmail(), + }); +}); + +const formatMimeEmail = () => { + return `From: Excited User + Subject: E2E test MIME email + Message-ID: <5ef155dc-f9b3-e83b-7c85-8f38aa6bafa0@mailgun.com> + Date: Thu, 06 Jun 2024 13:33:00 +0000 + MIME-Version: 1.0 + Content-Type: multipart/alternative; + boundary="--_NmP-42dc57efb261c5a6-Part_1" + + ----_NmP-42dc57efb261c5a6-Part_1 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: 7bit + + This is a MIME message + ----_NmP-42dc57efb261c5a6-Part_1 + Content-Type: text/html; charset=utf-8 + Content-Transfer-Encoding: 7bit + + HTML
version
of
the
body + ----_NmP-42dc57efb261c5a6-Part_1--`; +}; + +Then('the sendMimeEmail response contains information about the email', () => { + assert.equal(sendMimeEmailResponse.id, '<20240606154852.a3fafd8a5230e166@sandbox123.mailgun.org>'); + assert.equal(sendMimeEmailResponse.message, 'Queued. Thank you.'); +}); + +When('I send a request to retrieve a stored email', async () => { + getEmailResponse = await emailsApi.getEmail(domainName, 'storageKey'); +}); + +Then('the getEmail response contains the email details', () => { + assert.equal(getEmailResponse.from, 'sender@e2e.tst'); + assert.equal(getEmailResponse.to, '%recipient%'); + assert.equal(getEmailResponse.subject, 'Hello from mailgun'); + assert.equal(getEmailResponse.recipients, 'recipient@e2e.tst'); + assert.equal(getEmailResponse.strippedHtml, + '

Hello %recipient.name%

This is an HTML email'); + assert.equal(getEmailResponse.strippedText, 'Message text only'); + assert.equal(getEmailResponse.strippedSignature, ''); + assert.equal(getEmailResponse.bodyHtml, + '

Hello %recipient.name%

This is an HTML email'); + assert.equal(getEmailResponse.bodyPlain, 'Message text only'); + assert.deepEqual(getEmailResponse.deliveryTime, new Date('Thu, 06 Jun 2024 07:40:00 +0000')); + assert.equal(getEmailResponse.contentType, + 'multipart/alternative; boundary="44eea75a00c7df3bdd541c89727faec0ce8d5b09663245a35789d6b264c6"'); + assert.equal(getEmailResponse.contentTransferEncoding, undefined); + assert.equal(getEmailResponse.mimeVersion, '1.0'); +}); + +When('I send a request to get the sending queue status', async () => { + sendingQueuesStatusResponse = await emailsApi.getSendingQueuesStatus(domainName); +}); + +Then('the response contains the sending queues status', () => { + assert.equal(sendingQueuesStatusResponse.regular.isDisabled, false); + assert.equal(sendingQueuesStatusResponse.regular.disabled?.until, ''); + assert.equal(sendingQueuesStatusResponse.regular.disabled?.reason, ''); + assert.equal(sendingQueuesStatusResponse.scheduled.isDisabled, false); + assert.equal(sendingQueuesStatusResponse.scheduled.disabled?.until, ''); + assert.equal(sendingQueuesStatusResponse.scheduled.disabled?.reason, ''); +}); + +When('I send a request to purge the domain queues', async () => { + emailsApi.setStorageHostnames(['http://localhost:3021']); + purgeDomainQueuesResponse = await emailsApi.purgeDomainQueues(domainName); +}); + +Then('the response indicates the purge has been done', () => { + assert.equal(purgeDomainQueuesResponse.message, 'done'); +}); diff --git a/packages/mailgun/tests/rest/v1/mailgun-domain-api.test.ts b/packages/mailgun/tests/rest/v1/mailgun-domain-api.test.ts new file mode 100644 index 00000000..797ca939 --- /dev/null +++ b/packages/mailgun/tests/rest/v1/mailgun-domain-api.test.ts @@ -0,0 +1,85 @@ +import { MailgunDomainApi } from '../../../src/rest/v1/mailgun-domain-api'; +import { + ApiHostname, + BasicAuthenticationRequest, + MailgunCredentials, + MailgunRegion, +} from '@sinch/sdk-client'; + +describe('Mailgun API', () => { + let mailgunApi: MailgunDomainApi; + let params: MailgunCredentials & Pick; + const CUSTOM_HOSTNAME = 'https://new.host.name'; + + beforeEach(() => { + params = { + mailgunApiKey: 'API_KEY', + }; + }); + + it('should initialize the client', () => { + mailgunApi = new MailgunDomainApi(params, 'dummy'); + mailgunApi.getSinchClient(); + expect(mailgunApi.client).toBeDefined(); + expect(mailgunApi.client?.apiClientOptions.projectId).toBeUndefined(); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe('https://api.mailgun.net'); + expect(mailgunApi.client?.apiClientOptions.requestPlugins?.length).toBe(2); + }); + + it('should change the URL when specifying a different region', () => { + params.mailgunRegion = MailgunRegion.EUROPE; + mailgunApi = new MailgunDomainApi(params, 'dummy'); + mailgunApi.getSinchClient(); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe('https://api.eu.mailgun.net'); + }); + + it('should log a warning when using an unsupported region', async () => { + params.mailgunRegion = 'bzh'; + mailgunApi = new MailgunDomainApi(params, 'dummy'); + console.warn = jest.fn(); + mailgunApi.getSinchClient(); + expect(console.warn).toHaveBeenCalledWith( + 'The region "bzh" is not known as a supported region for the Mailgun API'); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe('https://api.bzh.mailgun.net'); + }); + + it('should use the hostname parameter', () => { + params.mailgunHostname = CUSTOM_HOSTNAME; + mailgunApi = new MailgunDomainApi(params, 'dummy'); + mailgunApi.getSinchClient(); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe(CUSTOM_HOSTNAME); + }); + + it('should update the hostname', () => { + mailgunApi = new MailgunDomainApi(params, 'dummy'); + mailgunApi.setHostname(CUSTOM_HOSTNAME); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe(CUSTOM_HOSTNAME); + }); + + it('should update the credentials', () => { + mailgunApi = new MailgunDomainApi(params, 'dummy'); + mailgunApi.setCredentials({ + mailgunApiKey: 'NEW_API_KEY', + }); + const basicAuthPlugin = mailgunApi.client?.apiClientOptions.requestPlugins?.find( + (plugin) => plugin instanceof BasicAuthenticationRequest, + ); + expect(basicAuthPlugin).toBeDefined(); + expect((basicAuthPlugin as any).password).toBe('NEW_API_KEY'); + }); + + it('should update the region', () => { + mailgunApi = new MailgunDomainApi(params, 'dummy'); + mailgunApi.getSinchClient(); + expect(mailgunApi.client).toBeDefined(); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe('https://api.mailgun.net'); + mailgunApi.setRegion(MailgunRegion.DEFAULT); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe('https://api.mailgun.net'); + mailgunApi.setRegion(MailgunRegion.EUROPE); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe('https://api.eu.mailgun.net'); + mailgunApi.setRegion('bzh'); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe('https://api.bzh.mailgun.net'); + mailgunApi.setRegion(''); + expect(mailgunApi.client?.apiClientOptions.hostname).toBe('https://api.mailgun.net'); + }); +}); diff --git a/packages/mailgun/tests/rest/v1/mailgun-service.test.ts b/packages/mailgun/tests/rest/v1/mailgun-service.test.ts new file mode 100644 index 00000000..88b44ea6 --- /dev/null +++ b/packages/mailgun/tests/rest/v1/mailgun-service.test.ts @@ -0,0 +1,44 @@ +import { SinchClientParameters } from '@sinch/sdk-client'; +import { EmailsApi, MailgunService } from '../../../src'; + +describe('Mailgun Service', () => { + + const CUSTOM_HOSTNAME = 'https://new.host.name'; + const CUSTOM_STORAGE_HOSTNAMES = [ + 'https://new-region.storage.name', + 'https://other-region.storage.name', + ]; + let params: SinchClientParameters; + + beforeEach(() => { + params = { + mailgunApiKey: 'API_KEY', + }; + }); + + it('should initialize all the APIs', () => { + // When + const mailgunService = new MailgunService(params); + + // Then + expect(mailgunService.emails).toBeInstanceOf(EmailsApi); + }); + + it('should set a custom hostname', () => { + // When + const mailgunService = new MailgunService(params); + mailgunService.setHostname(CUSTOM_HOSTNAME); + + // Then + expect(mailgunService.emails.getSinchClient().apiClientOptions.hostname).toBe(CUSTOM_HOSTNAME); + }); + + it('should set custom storage hostnames', () => { + // When + const mailgunService = new MailgunService(params); + mailgunService.setStorageHostnames(CUSTOM_STORAGE_HOSTNAMES); + + // Then + expect(mailgunService.emails.storageHostnames).toBe(CUSTOM_STORAGE_HOSTNAMES); + }); +}); diff --git a/packages/mailgun/tsconfig.build.json b/packages/mailgun/tsconfig.build.json new file mode 100644 index 00000000..73f1cf60 --- /dev/null +++ b/packages/mailgun/tsconfig.build.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"], + + "references": [{ "path": "../sdk-client" }], + + "ts-node": { + "esm": true + } +} diff --git a/packages/mailgun/tsconfig.json b/packages/mailgun/tsconfig.json new file mode 100644 index 00000000..2684d3e0 --- /dev/null +++ b/packages/mailgun/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "references": [ + { + "path": "tsconfig.build.json" + }, + { + "path": "tsconfig.tests.json" + } + ] +} diff --git a/packages/mailgun/tsconfig.tests.json b/packages/mailgun/tsconfig.tests.json new file mode 100644 index 00000000..2e454981 --- /dev/null +++ b/packages/mailgun/tsconfig.tests.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + + "compilerOptions": { + "rootDir": ".", + "outDir": "dist/tests" + }, + + "include": ["src/**/*.ts", "tests/**/*.ts"], + "exclude": ["node_modules", "dist"], + + "references": [{ "path": "../sdk-client" }], + + "ts-node": { + "esm": true + } +} diff --git a/packages/sdk-client/src/client/api-client-helpers.ts b/packages/sdk-client/src/client/api-client-helpers.ts index 4807191a..38921059 100644 --- a/packages/sdk-client/src/client/api-client-helpers.ts +++ b/packages/sdk-client/src/client/api-client-helpers.ts @@ -92,10 +92,18 @@ export const reviveDates = (input: any): any => { }; const isDateString = (value: any): boolean => { - if (typeof value === 'string' && value.length >= 10) { + // ISO 8601 + const iso8601DateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (typeof value === 'string' && value.length >= 10 && iso8601DateRegex.test(value.substring(0, 10))) { const date = new Date(value); return !isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value.slice(0,10); } + // RFC 2822 + const rfc2822DateRegex = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4}/; + if (typeof value === 'string' && value.length >= 16 && rfc2822DateRegex.test(value.substring(0, 16))) { + const date = new Date(value); + return !isNaN(date.getTime())&& date.toUTCString().slice(0, 16) === value.slice(0,16); + } return false; }; diff --git a/packages/sdk-client/src/domain/domain-helper.ts b/packages/sdk-client/src/domain/domain-helper.ts index 1f1f06bb..b2622ea5 100644 --- a/packages/sdk-client/src/domain/domain-helper.ts +++ b/packages/sdk-client/src/domain/domain-helper.ts @@ -6,6 +6,11 @@ export const CONVERSATION_TEMPLATES_HOSTNAME = `https://${REGION_PATTERN}templat export const ELASTIC_SIP_TRUNKING_HOSTNAME = 'https://elastic-trunking.api.sinch.com'; export const FAX_HOSTNAME = `https://${REGION_PATTERN}fax.api.sinch.com`; export const MAILGUN_HOSTNAME = `https://api.${REGION_PATTERN}mailgun.net`; +export const MAILGUN_STORAGE_HOSTNAMES = [ + 'https://storage-us-east4.api.mailgun.net', + 'https://storage-us-west1.api.mailgun.net', + 'https://storage-europe-west1.api.mailgun.net', +]; export const NUMBERS_HOSTNAME = 'https://numbers.api.sinch.com'; export const SMS_HOSTNAME = `https://${REGION_PATTERN}sms.api.sinch.com`; export const VERIFICATION_HOSTNAME = 'https://verification.api.sinch.com'; diff --git a/packages/sdk-client/tests/client/api-client-helpers.test.ts b/packages/sdk-client/tests/client/api-client-helpers.test.ts index 4aac59a0..c083b0fe 100644 --- a/packages/sdk-client/tests/client/api-client-helpers.test.ts +++ b/packages/sdk-client/tests/client/api-client-helpers.test.ts @@ -50,4 +50,9 @@ describe('API client helpers', () => { expect(reviveDates(obj)).toStrictEqual(expected); }); + it('should revive RFC 2822 dates', () => { + const date = 'Thu, 06 Jun 2024 07:40:00 +0000'; + expect(reviveDates(date)).toEqual(new Date('2024-06-06T07:40:00.000Z')); + }); + }); diff --git a/packages/sdk-core/package.json b/packages/sdk-core/package.json index 8d9d5c40..1dfeb2f6 100644 --- a/packages/sdk-core/package.json +++ b/packages/sdk-core/package.json @@ -1,6 +1,6 @@ { "name": "@sinch/sdk-core", - "version": "1.2.0", + "version": "1.3.0", "description": "Node.js client for the Sinch API platform", "homepage": "", "repository": { @@ -32,6 +32,7 @@ "@sinch/conversation": "^1.2.0", "@sinch/elastic-sip-trunking": "^1.2.0", "@sinch/fax": "^1.2.0", + "@sinch/mailgun": "^1.2.0", "@sinch/numbers": "^1.2.0", "@sinch/sms": "^1.2.0", "@sinch/verification": "^1.2.0", diff --git a/packages/sdk-core/src/index.ts b/packages/sdk-core/src/index.ts index f4683389..96e70b58 100644 --- a/packages/sdk-core/src/index.ts +++ b/packages/sdk-core/src/index.ts @@ -2,6 +2,7 @@ export * from './sinch-client'; export * from '@sinch/conversation'; export * from '@sinch/elastic-sip-trunking'; export * from '@sinch/fax'; +export * from '@sinch/mailgun'; export * from '@sinch/numbers'; export * from '@sinch/sms'; export * from '@sinch/verification'; diff --git a/packages/sdk-core/src/sinch-client.ts b/packages/sdk-core/src/sinch-client.ts index da27e38c..4dfcfc51 100644 --- a/packages/sdk-core/src/sinch-client.ts +++ b/packages/sdk-core/src/sinch-client.ts @@ -1,5 +1,6 @@ import { ConversationService } from '@sinch/conversation'; import { FaxService } from '@sinch/fax'; +import { MailgunService } from '@sinch/mailgun'; import { NumbersService } from '@sinch/numbers'; import { SmsService } from '@sinch/sms'; import { VerificationService } from '@sinch/verification'; @@ -13,6 +14,7 @@ export class SinchClient { public readonly conversation: ConversationService; public readonly fax: FaxService; public readonly elasticSipTrunking: ElasticSipTrunkingService; + public readonly mailgun: MailgunService; public readonly numbers: NumbersService; public readonly sms: SmsService; public readonly verification: VerificationService; @@ -27,6 +29,7 @@ export class SinchClient { this.conversation = new ConversationService(params); this.elasticSipTrunking = new ElasticSipTrunkingService(params); this.fax = new FaxService(params); + this.mailgun = new MailgunService(params); this.numbers = new NumbersService(params); this.sms = new SmsService(params); this.verification = new VerificationService(params); diff --git a/yarn.lock b/yarn.lock index 07f593bb..61514ba6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2486,6 +2486,13 @@ dependencies: undici-types "~5.26.4" +"@types/nodemailer@^6.4.16": + version "6.4.16" + resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.16.tgz#db006abcb1e1c8e6ea2fb53b27fefec3c03eaa6c" + integrity sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ== + dependencies: + "@types/node" "*" + "@types/normalize-package-data@^2.4.0": version "2.4.4" resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz" @@ -7047,6 +7054,11 @@ node-releases@^2.0.18: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +nodemailer@^6.9.16: + version "6.9.16" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.16.tgz#3ebdf6c6f477c571c0facb0727b33892635e0b8b" + integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ== + nopt@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d"