Emails 📧
Email providers
Emails which are sent via the Hub are sent using Mailgun.
Messages can also be sent directly to MS Teams.
Email settings in the Hub
Users can view and edit email settings, edit and preview email templates and add custom email templates via the Admin Dashboard.
Available variables for an email template are displayed in the Email Editor as Handlebar variables.


Sending emails
Email sending flow
When an email is sent via postTemplatedMessages() (api/modules/messenger.js), the app first writes a message to the Hub's inbox tables, then queues the email for delivery. Both are side effects of the same call, and the DB write happens before the email is queued.
- Data model:
messages(subject, content, type, template_id, thread_id, channel:email/ms_teams),recipients(per-usersent/readflags), andthreads(groups related messages) — seeapi/db/models/. mail-servicesends the email, then patchesrecipients.sent = 1on job completion (mail-service/lib/mail-sender.js).
Hub inbox
The message recorded above also appears in the Hub's in-app inbox, giving users a record of emails sent to them inside the product.
- MS Teams messages:
postTemplatedMessages()also delivers messages viasendMessageToMsTeams(), using the same message/recipient DB write, so Teams-delivered messages appear in the Hub inbox too. - API:
GET /api/messages(api/routes/messages/get.js) returns{unreadCount, messages}for the logged-in user, and excludesnewUser,invitationReminder,resetPassword, andverifyEmailAddressmessage types from the results — these emails are still recorded, they just don't appear in the visible inbox. Read state is updated viaPATCH /api/messages/:id(mark one as read) andPATCH /api/messages(mark all as read). - Frontend: the Hub's Inbox page (
frontend/src/main/components/pages/inbox/) uses a legacy Flux store (not react-query). New messages arrive either via HTTP long-polling (GET /api/poll/:userId, preferred, near-real-time) or via 60s interval polling as a fallback when long-polling is disabled.

Excluding message types from the inbox
Some templateTypes are transactional/system messages (account setup, password resets, email verification) rather than things a user needs a persistent inbox record of, so they're deliberately hidden from view.
The exclusion is a hardcoded '-type' filter, duplicated in both getMessages() and getUnreadMessagesCount() in api/routes/messages/get.js. The - prefix is dare's negation operator, compiled to SQL NOT IN (...). There's no shared constant for these type strings — to hide a new message type from the inbox, add it to both arrays.
Adding or updating email templates
We aim to have each email template available in all languages supported by the Hub.
Email templates and the email template translations are stored and maintained in the Hub email translations Google spreadsheet.
See the readme in the translations repo for instructions on how to:
- auto translate email templates in the spreadsheet
- copy them into the hub
- write a migration script to deploy them
In the Hub repo, email templates and their respective translations are stored under tools/db-migrate/emailTemplates and are added to the DB and updated in the DB using migrations.
See also: i18n
Flow
Migrations
The emailTemplates util should be utilised to update either all or selected email templates in the DB.
Usage example:
const Runner = require('../runner');
const emailTemplates = require('../emailTemplates');
const queries = [emailTemplates('userImport')]; // userImport email template is being updated
module.exports = new Runner(queries);