Skip to content

Commit bb74e37

Browse files
committed
initial commit
0 parents  commit bb74e37

33 files changed

+6469
-0
lines changed

.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["@babel/preset-env"]
3+
}

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[*.md]
12+
trim_trailing_whitespace = false

.eslintrc.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"root": true,
3+
"extends": "eslint:recommended",
4+
"env": {
5+
"node": true,
6+
"es6": true
7+
},
8+
"parser": "babel-eslint",
9+
"plugins": [
10+
"flowtype"
11+
],
12+
"parserOptions": {
13+
"ecmaVersion": 6,
14+
"sourceType": "module"
15+
},
16+
"rules": {
17+
"indent": ["error", 2, { "SwitchCase": 1 }],
18+
"linebreak-style": ["error", "unix"],
19+
"no-trailing-spaces": 2,
20+
"eol-last": 2,
21+
"space-in-parens": ["error", "never"],
22+
"no-multiple-empty-lines": 1,
23+
"prefer-const": "error",
24+
"space-infix-ops": "error",
25+
"no-useless-escape": "off",
26+
"require-atomic-updates": "off"
27+
}
28+
}

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
6+
# Runtime data
7+
pids
8+
*.pid
9+
*.seed
10+
11+
# Coverage
12+
lib-cov
13+
coverage
14+
.nyc_output
15+
16+
# Node
17+
.lock-wscript
18+
build/Release
19+
node_modules
20+
.node_repl_history
21+
.npm
22+
23+
# Temporary
24+
.idea
25+
.DS_Store
26+
lib
27+
28+
# Visual Studio Code
29+
.vscode
30+
31+
# Demo configuration
32+
mailgun.json

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2021 Manuel Trezza
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# parse-server-api-mail-adapter
2+
3+
[![npm version](https://badge.fury.io/js/parse-server-api-mail-adapter.svg)](https://badge.fury.io/js/parse-server-api-mail-adapter)
4+
5+
The Parse Server API Mail Adapter enables Parse Server to send emails using any 3rd party API with built-in dynamic templates and localization.
6+
7+
8+
# Content
9+
10+
- [Getting Started](#getting-started)
11+
- [Demo](#demo)
12+
- [Configuration](#configuration)
13+
- [Templates](#templates)
14+
- [Localization](#localization)
15+
- [Need help?](#need-help)
16+
17+
# Getting Started
18+
19+
1. Install adapter:
20+
```
21+
npm install --save parse-server-api-mail-adapter
22+
```
23+
2. Add [template files](#templates) to a subdirectory.
24+
2. Add [adapter configuration](#configuration) to Parse Server.
25+
26+
# Demo
27+
28+
The demo script makes it easy to test adapter configurations and templates by sending emails without Parse Server via the email service provider [Mailgun](https://www.mailgun.com):
29+
30+
1. Create a file `mailgun.json` in the `demo` directory with the following content:
31+
```js
32+
{
33+
"key": "MAILGUN_API_KEY", // e.g. abc123
34+
"domain": "MAILGUN_DOMAIN", // e.g. [email protected]
35+
"sender": "SENDER_EMAIL", // e.g. [email protected]
36+
"recipient": "RECIPIENT_EMAIL" // e.g. [email protected]
37+
}
38+
```
39+
2. Run `node ./demo` to execute the script and send an email.
40+
41+
You can modify the script to use any other API you like or debug-step through the sending process to better understand the adapter internals.
42+
43+
# Configuration
44+
45+
An example configuation to add the API Mail Adapter to Parse Server could look like this:
46+
47+
```js
48+
// Declare a mail client
49+
const mailgun = require('mailgun.js');
50+
const mailgunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY });
51+
const mailgunDomain = process.env.MAILGUN_DOMAIN;
52+
53+
// Configure Parse Server
54+
const server = new ParseServer({
55+
...otherOptions,
56+
57+
emailAdapter: {
58+
module: 'parse-server-api-mail-adapter',
59+
options: {
60+
// The email address from which email are sent.
61+
sender: '[email protected]',
62+
// The email templates.
63+
templates: {
64+
// The template used by Parse Server to send an email for password reset; this is a reserved template name.
65+
passwordResetEmail: {
66+
subjectPath: './files/password_reset_email_subject.txt'),
67+
textPath: './files/password_reset_email.txt'),
68+
htmlPath: './files/password_reset_email.html')
69+
},
70+
// The template used by Parse Server to send an email for email address verification; this is a reserved template name.
71+
verificationEmail: {
72+
subjectPath: './files/verification_email_subject.txt'),
73+
textPath: './files/verification_email.txt'),
74+
htmlPath: './files/verification_email.html')
75+
},
76+
// A custom email template that can be used when sending emails from Cloud Code; the template name can be choosen freely; it is possible to add various custom templates.
77+
customEmail: {
78+
subjectPath: './files/custom_email_subject.txt'),
79+
textPath: './files/custom_email.txt'),
80+
htmlPath: './files/custom_email.html'),
81+
// Placeholders contain the values to be filled into the placeholder keys in the file content. A placeholder `{{appName}}` in the email will be replaced the value defined here.
82+
placeholders: {
83+
appName: "ExampleApp"
84+
},
85+
// Extras to add to the email payload that is accessible in the `apiCallback`.
86+
extra: {
87+
replyTo: '[email protected]'
88+
},
89+
// A callback that makes the Parse User accessible and allows to return user-customized placeholders that will override the default template placeholders.
90+
placeholderCallback: async (user) => {
91+
return {
92+
phone: user.get('phone')
93+
};
94+
},
95+
// A callback that makes the Parse User accessible and allows to return the locale of the user for template localization.
96+
localeCallback: async (user) => {
97+
return user.get('locale');
98+
},
99+
// Is true if localization of template files should be enabled.
100+
enableLocalization: true
101+
}
102+
},
103+
// The asynronous callback that contains the composed email payload to be passed on to an 3rd party API. The payload may need to be convert specifically for the API; conversion for common APIs is conveniently available in the `ApiPayloadConverter`. Below is an example for the Mailgun client.
104+
apiCallback: async (payload) => {
105+
const mailgunPayload = ApiPayloadConverter.mailgun(payload);
106+
await mailgunClient.messages.create(mailgunDomain, mailgunPayload);
107+
}
108+
}
109+
}
110+
});
111+
```
112+
113+
## Templates
114+
115+
Emails are composed using templates. A template defines the paths to its content files, for example:
116+
117+
```js
118+
templates: {
119+
exampleTemplate: {
120+
subjectPath: './files/custom_email_subject.txt'),
121+
textPath: './files/custom_email.txt'),
122+
htmlPath: './files/custom_email.html'),
123+
}
124+
},
125+
```
126+
127+
There are different files for different parts of the email:
128+
- subject (`subjectPath`)
129+
- plain-text content (`textPath`)
130+
- HTML content (`htmlPath`)
131+
132+
# Localization
133+
134+
Localization allows to use a specific template depending on the user locale. To turn on localization for a template, add a `localeCallback` to the template configuration.
135+
136+
The locale returned by `localeCallback` will be used to look for locale-specific template files. If the callback return an invalid locale or nothing at all (`undefined`), localization will be ignored and the default files will be used.
137+
138+
The locale-specific files are placed in subfolders with the name of either the whole locale (e.g. `de-AT`), or only the language (e.g. `de`). The locale has to be in format `[language]-[country]` as specified in [IETF BCP 47](https://tools.ietf.org/html/bcp47), e.g. `de-AT`.
139+
140+
Localized files are placed in subfolders of the given path, for example:
141+
```
142+
path/
143+
├── example.html // default file
144+
└── de/ // de language folder
145+
│ └── example.html // de localized file
146+
└── de-AT/ // de-AT locale folder
147+
│ └── example.html // de-AT localized file
148+
````
149+
150+
Files are matched with the user locale in the following order:
151+
1. Locale match, e.g. locale `de-AT` matches file in folder `de-AT`.
152+
2. Language match, e.g. locale `de-AT` matches file in folder `de`.
153+
3. Default match: file in base folder is returned.
154+
155+
# Need help?
156+
157+
- Search through existing issues or open a new issue.
158+
- Ask on StackOverflow using the tag `parse-server` and `api-mail-adapter`.

demo/index.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* ==============================================================
3+
* Demo script to send an email using the Mailgun API.
4+
* ==============================================================
5+
* Instructions:
6+
*
7+
* 1. Create a file `mailgun.json` in the root directory with the
8+
* following keys to configure the test script:
9+
* ```
10+
* {
11+
* key: "xxx", // The Mailgun API key.
12+
* domain: "xxx", // The Mailgun domain.
13+
* host: "xxx", // The Mailgun host.
14+
* sender: "xxx", // The email sender.
15+
* recipient: "xxx", // The email recipient.
16+
* }
17+
* ```
18+
*
19+
* 2. Run this script with `node ./demo` to send the email.
20+
* ==============================================================
21+
*/
22+
23+
const ApiMailAdapter = require('../src/ApiMailAdapter');
24+
const ApiPayloadConverter = require('../src/ApiPayloadConverter');
25+
const mailgun = require('mailgun.js');
26+
const path = require('path');
27+
28+
const {
29+
key,
30+
domain,
31+
host,
32+
sender,
33+
recipient
34+
} = require('./mailgun.json');
35+
36+
const mailgunClient = mailgun.client({ username: 'api', key: key });
37+
const filePath = (file) => path.resolve(__dirname, '../spec/templates/', file);
38+
const config = {
39+
sender: sender,
40+
templates: {
41+
passwordResetEmail: {
42+
subjectPath: filePath('password_reset_email_subject.txt'),
43+
textPath: filePath('password_reset_email.txt'),
44+
htmlPath: filePath('password_reset_email.html')
45+
},
46+
verificationEmail: {
47+
subjectPath: filePath('verification_email_subject.txt'),
48+
textPath: filePath('verification_email.txt'),
49+
htmlPath: filePath('verification_email.html')
50+
},
51+
customEmail: {
52+
subjectPath: filePath('custom_email_subject.txt'),
53+
textPath: filePath('custom_email.txt'),
54+
htmlPath: filePath('custom_email.html'),
55+
placeholders: {
56+
username: "DefaultUser",
57+
appName: "DefaultApp"
58+
},
59+
extra: {
60+
replyTo: '[email protected]'
61+
}
62+
}
63+
},
64+
apiCallback: async (payload) => {
65+
const mailgunPayload = ApiPayloadConverter.mailgun(payload);
66+
await mailgunClient.messages.create(domain, mailgunPayload);
67+
}
68+
};
69+
70+
const adapter = new ApiMailAdapter(config);
71+
72+
adapter.sendMail({
73+
templateName: 'customEmail',
74+
recipient: recipient,
75+
placeholders: {
76+
appName: "ExampleApp",
77+
username: "ExampleUser"
78+
},
79+
direct: true
80+
});

0 commit comments

Comments
 (0)