We recently started with Redwood for an MVP, and we’re really liking it so far. Thought I would share how we are sending emails with RedwoodJS + Faktory, inspired by ActionMailer
After following these guides, Creating a Background Worker with Exec and Faktory and Sending Emails, I wanted the mailer to be more reusable and remove duplication. To do this, we started with setting up an ApplicationMailer
in lib/mailers/applicationMailer.ts
.
export default class ApplicationMailer {
to: string
constructor(to) {
this.to = to
}
sendEmail = async (template, emailArgs) => {
const html = await generateHTML(template, emailArgs)
const text = await generateTextFromHTML(html)
const emailOptions = {
to: this.to,
subject: emailArgs.subject,
text,
html,
}
}
}
const generateHTML = async (template, emailArgs) => {
...
}
const generateTextFromHTML = async (html) => {
...
}
Then, we created a UserMailer
in lib/mailers/user.ts
that extends ApplicationMailer
for sending any emails related to users
export default class UserMailer extends ApplicationMailer {
confirmationEmail = async ({
firstName,
confirmationToken,
}: ConfirmationEmailOptions) => {
const confirmationURL = `${process.env.MAILER_URL}/confirm/${confirmationToken}`
const emailArgs = {
subject: 'Please confirm your email',
firstName: firstName,
confirmationURL: confirmationURL,
}
this.sendEmail(ConfirmationTemplate, emailArgs)
}
}
confirmationEmail
is just one of the functions we have in UserMailer
, we can add as many as we want with minimal effort now. The UserMailer
is only responsible for building arguments needed for the email we want to send, and sendEmail
takes care of the rest.
Now when we want to send an email from a service or function, we can do something like:
const mailer = new UserMailer(user.email)
const emailArgs = { firstName: 'firstName', confirmationToken: 'confirmationToken' }
await mailer.confirmationEmail({ ...emailArgs })
rather than defining a function to build emails in a service.
Next step was to include Faktory, which after following the guide above required some extra code in ApplicationMailer.sendEmail
.
export default class ApplicationMailer {
...
const emailOptions = {
...
}
const client = await faktory.connect()
await client.job('sendEmailTask', { ...emailOptions }).push()
await client.close()
}
And adding the sendEmailTask
in lib/tasks/mailer.ts
which follows the same method in the Sending Email tutorial above.
Now we have a reusable mailer that is easy to add new emails to, and a bit closer to ActionMailer style which I’m used to.
I hope this helps anyone looking to send emails / organise email structure in Redwood.js, this is my first post in the community so any feedback (good or bad) would be great.
Note: We’ve also been using Maizzle to generate email templates which has been great. We put email templates in lib/mailers/[resource]/templates
and then import them in the mailer we need. Here’s an example for reference.
type ConfirmationTemplateOptions = {
firstName: string
confirmationURL: string
}
export const ConfirmationTemplate = ({
firstName,
confirmationURL,
}: ConfirmationTemplateOptions) => {
return `
[template html here]
...
`
}