Microservice to send emails
This microservice was made because we often have problems sending emails or we need a default backend that will do this for us.
This is why Zaqar was built. To become a fast approach to email handling in Node using microservices.
Important: Zaqar is not meant to be used as a library, so there's no instalation instructions that eventually would led you to an npm install
.
Zaqar was created to be a fully implemented microservice, in other words, you have to run it instead of installing it as a library, that's why there's a Docker Image you should pull and run within your infrastructure.
- The image exposes port 3000
Zaqar also comes with a helm chart so you can run it in a kubernetes infrastructure, this helm chart is located in this same repository so you can "run":
helm repo add zaqar https://lsantos.me/zaqar/helm
This is going to add Zaqar to your helm repo list. Then you can "run":
If you're running helm < 3.0
you should pass a --name
:
helm install zaqar/zaqar --name=zaqar-mail-server --set "environment.SENDGRID_APIKEY=key" ...
Otherwise, if you are on Helm version >3.0
:
helm install zaqar-mail-server zaqar/zaqar --set "environment.SENDGRID_APIKEY=key" ...
Zaqar is exposed locally only, this means you will not be able to access it externally unless you manually create an Ingress. This is due to the best practices where microservices should only communicate with each other in the local network
Click the button below to deploy zaqar to Heroku
You should set some environment variables:
SENDGRID_APIKEY
: As of now, Zaqar only accepts Sendgrid as mail sender, so this is where you put your API keyDEFAULT_FROM_ADDRESS
: The email to be the "from" address in case there's no from address in the email.DEFAULT_FROM_NAME
: The name to be the "from" name in case there's no name specified.RENDERER_LIST
: A space-separated list of renderer packages to be loaded on load (see renderers section for more details)AUTH_USERNAME
: If provided, will set the basic auth usernameAUTH_PASSWORD
: If provided, will set the basic auth password
If both AUTH_USERNAME
and AUTH_PASSWORD
are provided, Zaqar will enter the auth mode. This mode only allows requests with the Authorization Basic
header set, if the user or the password do not match, the service will return 401
.
If one of the two variables are not set, Zaqar will run in authless mode.
Zaqar only has the POST /send
endpoint which takes the following "payload":
{
"to": ["to@email.com"],
"from": "my@email.com",
"subject": "subject",
"template": {
"lang": "renderer-language", // Renderer to be used
"text": "your {{template-like}} <% structure %>" // See renderer section
},
"cc": ["one@email.com"],
"bcc": ["two@email.com"],
"replyTo": "email@email.com"
}
You can also send "complex" email fields with given names:
{
"to": [{email: "to@email.com", name: "Someone"}],
"from": {email: "to@email.com", name: "Someone"},
"subject": "subject",
"template": {
"lang": "renderer-language", // Renderer to be used
"text": "your {{template-like}} <% structure %>" // See renderer section
},
"cc": ["one@email.com"],
"bcc": ["two@email.com"],
"replyTo": "email@email.com"
}
Only 'to', 'subject', 'template'
fields are required.
Following the schema:
{
type: 'object',
properties: {
from: { oneOf: [{ type: 'string', format: 'email' }, { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string', format: 'email' } } }] },
to: {
type: 'array',
items: {
anyOf: [{ type: 'string', format: 'email' }, { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string', format: 'email' } } }]
}
},
subject: {
type: 'string'
},
template: {
type: 'object',
properties: {
text: { type: 'string' },
lang: { type: 'string' }
},
additionalProperties: false,
required: ['text', 'lang']
},
cc: {
type: 'array',
items: {
anyOf: [{ type: 'string', format: 'email' }, { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string', format: 'email' } } }]
}
},
data: {
type: 'object'
},
replyTo: {
oneOf: [{ type: 'string', format: 'email' }, { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string', format: 'email' } } }]
},
bcc: {
type: 'array',
items: {
anyOf: [{ type: 'string', format: 'email' }, { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string', format: 'email' } } }]
}
}
},
required: ['to', 'subject', 'template'],
additionalProperties: false
}
The
data
key is reserved to template data variables, so if you have a variable calledusername
in your email, you should send a{ data: { username: "user" } }
in the payload.
If
from
is not filled, then the value ofDEFAULT_FROM_ADDRESS
andDEFAULT_FROM_NAME
variables will be used
In case there's no
replyTo
thefrom
data will be used.
Which answers:
{
"to": [
"lucas@test.com"
],
"from": "from@email.com",
"subject": "test",
"bcc": [],
"cc": [],
"template": "<p>test</p>"
}
The whole API is described at the api documentation
Zaqar is extensible, which means you can use different renderers to render your email using different templating engines. The template engine to be used for an specific email is set by the lang
key in the template
object within the /send
payload.
Renderers can be load by the environment variable called RENDERER_LIST
, which receive a space-separated list of renderer package names. For example, if we wanted to load both the mustache and ejs template engines we could set it to: zaqar-renderer-mustache zaqar-renderer-ejs
.
Renderers will be loaded everytime a new instance of Zaqar is spinned up. There's no cache, Zaqar uses NPM to install the packages within the strucutre.
If this environment variable is not set, Zaqar will set it to
zaqar-renderer-mustache zaqar-renderer-ejs
by default
A renderer is just a simple JavaScript function which takes the following signature:
async function renderer (text: string, data: any = {}, renderer: typeof yourRenderer = yourRenderer): Promise<string>
The
renderer
parameter is completely optional and is there just for testing purposes
The file should export an object with the following keys:
{
name: 'mustache', // name of the renderer, this will be the "lang" parameter in the API
fn: renderFunction, // Function to be used as renderer
errClass: MustacheRendererError // Any additional data you want to share with external libraries
}
Take a look at this example taken from Mustache Renderer for Zaqar:
import mustache from 'mustache'
export class MustacheRendererError extends Error {
constructor (message: string) {
super(`[Zaqar renderer error - Mustache]: ${message}`)
}
}
async function renderFunction (text: string, data: any = {}, renderer: typeof mustache = mustache): Promise<string> {
try {
return renderer.render(text, data)
} catch (error) {
throw new MustacheRendererError(error.message)
}
}
const rendererObj = {
name: 'mustache',
fn: renderFunction,
errClass: MustacheRendererError
}
export default rendererObj
module.exports = rendererObj
Then publish on NPM using any name. Then add it to RENDERER_LIST
to be loaded.
These are the currently supported renderers for Zaqar:
- zaqar-renderer-mustache:
- Lang: mustache
- zaqar-renderer-ejs:
- Lang: ejs
- zaqar-renderer-pug:
- Lang: pug
If you want to add yours, please send a PR :)