Skip to main content


Mailers in Hyperstack takes care of every aspect of email sending.

That means building emails using templates that are safe for many email clients, sending mutiple formats such as text and HTML, choosing the sending service, and testing emails -- which is notoriously hard.

Using mailers

Like workers, you can import your mailer class and use it from anywhere. Mailers in Hyperstack use the same framework as workers, so when you send them, they'll enqueue:

// in Rails this is AuthMailer.with(user).send_welcome.deliver_later
await AuthMailer.sendWelcome(user).deliverLater()

We await just the scheduling of the email sending, which takes just the time it takes to place a value in Redis.

In in-process mode, this await will wait right there until the actual job finishes.

You can always use await AuthMailer.sendWelcome(user).deliverNow() to perform the sending right then and there.

Building mailers

A mailer is a Typescript class and a folder that contains templates. Every mailer looks like this:

  ,---- you can pick your folder name
welcome/ <---- always contains the following file names:
auth.ts <---- will use `auth/welcome` as the template name.

And a mailer looks like this:

import { Mailer } from 'hyperstack'
import type { User } from '../models/user'

class AuthMailer extends Mailer {
static defaults = {
from: `Elle Postage <>`,
static sendWelcome(user: Partial<User>) {
return this.mail({
template: 'auth/welcome',
message: {
to: user.username,
locals: {
verifyToken: user.emailVerificationToken,

export { AuthMailer }

While a template, is an EJS template, and looks like this:

You can <a href="<%= verifyToken %>">verify your account</a>

Lastly, export your mailer in the index.ts file:

export { AuthMailer } from './auth'


In addition to the standard worker configuration, which determines how to enqueue jobs (in this case email sending jobs), you have a few more options for mailers specifically:

mailers: {
send: true,
delivery: 'test',
preview: true,
smtpSettings: {
host: 'localhost',
port: 1025,

Combining these you can create various run modes:

  • For production: send: true, delivery: 'smtp', preview: false and populate the smtpSettings section.
  • For development: if you use MailHog, then same settings as production, just point to localhost. Otherwise, send: true, preview: true will open up the sent email in your browser for review.
  • For test: same as development, just preview: false.


Testing mailer has a special construct in Hyperstack. Sending emails goes to an in-memory mailbox, which you can later assert on.

You set up data, deliver() (which delivers immediately), and follow up assertions and snapshotting on deliveries(), which contains all of the deliveries for the test run up to now.

There's an even shorter way to test, which is just to return a delivery to the testing framework (here: we're returning it with sendWelcome(...)).

import { test } from '@hyperstackjs/testing'
import { AuthMailer } from '../../app/mailers'
import { root } from '../../config/settings'
import { appContext } from '@/app'

const {
serializers: { baseMailerSerialize },
} = test(root)
describe('mailers', () => {
describe('welcome', () => {

// a 'returns delivery' mailer test
mailers('should send welcome', async () =>
username: '',
name: 'joe',

// a more elaborate mailer test
mailers('should send welcome: manual', async () => {
// can also grab a mailer from app context
const { AuthMailer } = appContext.mailers()
await AuthMailer.sendWelcome({
username: '',
name: 'joe',


Get StartedBuilding APIsModeling DataBackground JobsTestingDeploymentFAQ