Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ This adapter supports any REST API by adapting the API payload in the adapter co
For convenience, support for common APIs is already built into this adapter and available via the `ApiPayloadConverter`. The following is a list of currently supported API providers:

- [Mailgun](https://www.mailgun.com)
- [AWS SES - v3 SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ses/index.html)

If the provider you are using is not already supported, please feel free to open a PR.

Expand Down Expand Up @@ -277,6 +278,59 @@ const server = new ParseServer({
});
```

### Example for AWS SES v3 sdk

This is an example for the SES client for the **v3 SDK**. Please note that v2 works quite differently:

```js
// Configure mail client
const { SES, SendEmailCommand } = require("@aws-sdk/client-ses");

// `fromInstanceMetadata` is for dynamically getting credentials via IMDS on your AWS instance
// `fromEnv` grabs your credentials from environment variables (I use this method for testing and the former for production)
const { fromInstanceMetadata, fromEnv } = require("@aws-sdk/credential-providers");

let awsCredsProvider;
if (process.env.NODE_ENV && process.env.NODE_ENV === "production") {
awsCredsProvider = fromInstanceMetadata();
} else {
awsCredsProvider = fromEnv();
}

// `awsCredsProvider` returns a promise, once resolved, you get your credentials
const getCreds = async () => {
return await awsCredsProvider();
};

// Perhaps there's a better way to do this. Top level await will be amazing.
getCreds().then(credentials => {
const sesClient = new SES({
// credentials from `awsCredsProvider` provider
credentials,
region: 'eu-west-1',
apiVersion: '2010-12-01'
});

// Configure Parse Server
const server = new ParseServer({
...otherServerOptions,

emailAdapter: {
module: 'parse-server-api-mail-adapter',
options: {
... otherAdapterOptions,

apiCallback: async ({ payload, locale }) => {
const awsSESPayload = ApiPayloadConverter.awsSES(payload);
const command = new SendEmailCommand(awsSESPayload);
await sesClient.send(command);
}
}
}
});
})
```

## Custom API

This is an example of how the API payload can be adapted in the adapter configuration `apiCallback` according to a custom email provider's API specification.
Expand Down
12 changes: 11 additions & 1 deletion spec/ApiMailAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,17 @@ describe('ApiMailAdapter', () => {
expect(payload.subject).toBe(examplePayload.subject);
expect(payload.text).toBe(examplePayload.text);
expect(payload.html).toBe(examplePayload.html);
})
});

it('converts payload for AWS SES v3 SDK', () => {
const payload = converter.awsSES(examplePayload);
expect(payload.Source).toEqual([examplePayload.from]);
expect(payload.Destination.ToAddresses).toEqual([examplePayload.to]);
expect(payload.ReplyToAddresses).toEqual([examplePayload.replyTo]);
expect(payload.Message.Subject.Data).toBe(examplePayload.subject);
expect(payload.Message.Body.Text.Data).toBe(examplePayload.text);
expect(payload.Message.Body.Html.Data).toBe(examplePayload.html);
});
});

describe('invoke _sendMail', function () {
Expand Down
77 changes: 77 additions & 0 deletions src/ApiPayloadConverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,83 @@ class ApiPayloadConverter {

return payload;
}

/**
* @description Converts the mail payload for the official AWS v3 SDK.
* @param {Object} originalPayload The original payload (provider agnostic).
* @returns {Object} The payload according to AWS SDK specification.
*/
static awsSES(originalPayload) {

// Clone payload
const payload = Object.assign({}, originalPayload);

// Substitute keys
if (payload.to) {
payload.Destination = {
ToAddresses: [payload.to]
};
delete payload.to;
}

if (payload.from) {
payload.Source = [payload.from];
delete payload.from;
}

if (payload.replyTo) {
payload.ReplyToAddresses = [payload.replyTo];
delete payload.replyTo;
}

if (payload.subject) {
if (!payload.Message) {
payload.Message = {}
}

if (!payload.Message.Subject) {
payload.Message.Subject = {}
}

payload.Message.Subject.Data = payload.subject;
payload.Message.Subject.Charset = 'UTF-8';
delete payload.subject;
}

if (payload.html && payload.html !== '') {
if (!payload?.Message) {
payload.Message = {}
}

if (!payload.Message.Body) {
payload.Message.Body = {}
}

payload.Message.Body.Html = {
Charset: 'UTF-8',
Data: payload.html
};
delete payload.html;
}

if (payload.text && payload.text !== '') {
if (!payload?.Message) {
payload.Message = {}
}

if (!payload.Message.Body) {
payload.Message.Body = {}
}

payload.Message.Body.Text = {
Charset: 'UTF-8',
Data: payload.text
};
delete payload.text;
}

return payload;
}
}

module.exports = ApiPayloadConverter;