Module - Mail
To it’s core, firstly provides you the ability to send emails. For this, we didn’t reinvent the
wheel and use the great nodemailer package.
Once you have it setup, assign you the role "Mail.Admin" (You can get it via
Roles_Mail.Mail_Admin), and in Admin UI, you will be able to see all mails in the entity named
FF Mails.
Installation
Section titled “Installation”npm add firstly@latest -Dimport { mail } from 'firstly/mail/server'
export const api = remultApi({ modules: [mail()],})Anywhere in your code you can then:
import { remult } from 'remult'
await remult.context.sendMail('my_first_mail', { to: 'hello@example.com', subject: 'Hello from firstly', sections: [ { html: 'hello <b>world</b> 👋' }, { html: 'Did you star remult repo ?', cta: { html: 'Star it', link: 'https://github.com/remult/remult' }, }, ],})The result will be something like this:

You can see the structure of the mail in the following image:

Manually configure your service
Section titled “Manually configure your service”Configure the transport of your email service.
export const api = remultApi({ modules: [ mail({ nodemailer: { transport: { host: '...', port: 587, secure: false, // Use `true` for port 465, `false` for all other ports auth: { user: '...', pass: '...', }, }, }, }), ],})Send via Resend
Section titled “Send via Resend”Resend speaks SMTP, so it drops in as a regular nodemailer transport. Sign up, verify your sending domain (Resend gives you the SPF / DKIM / DMARC DNS records to paste into your registrar), grab an API key, then:
// Load RESEND_API_KEY from your framework's env helper:// SvelteKit: import { RESEND_API_KEY } from '$env/static/private'// Next / Node: const RESEND_API_KEY = process.env.RESEND_API_KEY!
export const api = remultApi({ modules: [ mail({ from: { name: 'My Cool App', address: 'noreply@mycoolapp.com' }, nodemailer: { transport: { host: 'smtp.resend.com', port: 465, secure: true, auth: { user: 'resend', // literal string, not your account email pass: RESEND_API_KEY, }, }, }, }), ],})Demo admin UI (Svelte)
Section titled “Demo admin UI (Svelte)”Two drop-in Svelte 5 components for a quick “send + browse” admin page. Raw Tailwind only, no plugin required.
<script lang="ts"> import { LastMails, WriteMail } from 'firstly/mail'</script>
<WriteMail /><LastMails limit={30} />Both gate on the Mail.Admin role (assigned via the auth boutique’s
addRolesToUser helper or SUPER_ADMIN_EMAILS). Users without the role see an
inline notice instead of the form / list, so it’s safe to land on a route any
authenticated user can reach.
<LastMails /> calls repo(Mail).find({ limit }) on mount and on a Refresh
button click - sorted createdAt desc (entity default). It exposes
refresh() so you can bind:this and call it from a sibling after a manual
send if you want.
Global params
Section titled “Global params”Global params are applied to all mails by default and can be overridden for each mail (handy!)
export const api = remultApi({ modules: [ mail({ service: 'Cool App', footer: `Thank you for using Cool App`, // primaryColor: '#000000', // secondaryColor: '#000000', // toHtml(mailInfo) => `` // You can override the html of the mail from: { name: 'My Cool App', address: 'noreply@coolApp.com', }, }), ],})