Security Best Practices
Exposing your email templates to the world, can be risky if not properly secured. If credentials or authentication are mismanaged, others may be able to view or even send emails on your behalf.
Like any full-stack application, Brail may require authentication or authorisation, at different levels of the stack. This document provides a few tips, recipes and considerations to help you achieve the level of security you need.
Security concerns can be grouped as:
- Viewing Templates
- Interacting with templates
The below best practices are not mutually exclusive. You can use any combination of them to achieve the level of security you need.
Viewing Templates
The following discusses protecting the frontend portion of your Brail apps. If you are only using Brail as an API, you can skip this section.
If you have deployed your Brail frontend, your templates will be visible via the internet. For some use cases, this may be perfectly fine. However, there are several ways to protect your templates from being viewed by unauthorized users.
For example, you might surround your frontend code with an authentication layer:
// Next.js: _app.tsx
export default (props) => {
const { Component, pageProps } = props
return <AuthGuard> {/* 👈 User-supplied guard that authenticates users before revealing templates */}
<Component {...pageProps}/>
</AuthGuard>
}
You could also just entirely obscure the frontend aspects of Brail in production:
// Next.js: _app.tsx
export default (props) => {
const { Component, pageProps } = props
return <>
{process.env.NODE_ENV !== "production" && <Component {...pageProps}/>}
</>
}
Interacting with Templates
Protecting how people interact with your templates is very important, as it can prevent unauthorized users from sending emails on your behalf.
User-provided API Keys
Instead of storing your credentials within the Brail project, a very secure approach is to require API keys with every request. This way, your Brail project is kept ignorant of any sensitive credentials, and only credential-holders can ever send emails.
One way to achieve this is to add an apiKey
property to your metadata object when sending a template. This can then be forwarded in your send function.
const metaSchema = z.object({
// ... other properties
apiKey: z.string()
})
template
.meta(metaSchema)
.onSend(async args => {
// Optionally add validation logic here
await sendEmail(args.html, {
apiKey: args.meta.apiKey // 👈 Use the provided API key
to: args.meta.to
})
})
API Middleware
If your Brail templates are exposed via an API, you can implement authentication via middleware provided by your the API framework you're using. We provide an example of this in the tRPC section. This is useful for protecting any form of unauthorised interaction with your API.
SDK Environment Variables
If you are publishing your templates as an SDK, it's best to store any API keys or other sensitive credentials in environment variables. This way SDK users cannot send emails using your credentials.
Devtools
If you're not using the Brail devtools, you can skip this section.
By default, the Brail devtools are disabled in production environments. However they can be re-enabled via the isEnabled
option, passed to the initDevtools
function.
Because devtools uses it's own internal server to support it's sending functionality, any middleware you may have applied to your own API will not apply to the devtools.
However, user-provided keys will still be respected, and can be used to protect from sending emails in production.
In future releases, the Devtools will have support for more granular security.