SolidX

Email Templates

Metadata schema for populating email templates in SolidX applications.

Where it lives
JSON Pointer: /emailTemplates
JSONPath: $.emailTemplates
Parent: Root of the metadata file

Overview

Email Templates in SolidX allow you to create and manage HTML/Text based email templates with dynamic content and attachments.

Example: Email Templates Metadata

Below is an example configuration for two email templates: one for sending payment reminders and another for OTP verification. The body of the email templates is stored in separate HTML files i.e (specified in the body attribute)

{
  ..., // Other metadata
  "emailTemplates": [
  {
    "name": "new-payment-or-payment-reminder",
    "displayName": "Fees Portal: New Payment or Reminder",
    "body": "new-payment-or-payment-reminder.handlebars.html",
    "subject": "Reminder for a payment",
    "description": "Reminder email for pending payments",
    "active": true,
    "type": "text"
  },
  {
    "name": "otp-verification",
    "displayName": "Fees Portal: Otp Verification",
    "body": "otp-verification.handlebars.txt",
    "subject": "One time password for login",
    "description": "Send OTP email to parent for login.",
    "active": true,
    "type": "text"
  }
  ]
}

Example : Email Template File

Below are examples of email template files that can be referenced in the body attribute of the email template metadata.

This example uses Handlebars syntax for dynamic content insertion.

The variables used in this template (like {{student.studentName}}, {{dueDetails.totalAmountDue}}, etc.) should correspond to the data structure passed when sending the email.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Payment Notification</title>
  <style>
    body {margin:0; padding:0; background:#e8e8e8; font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif; color:#333;}
    .email-container {max-width:600px; margin:40px auto; background:#fff; border-radius:8px; overflow:hidden; box-shadow:0 4px 12px rgba(0,0,0,0.1);}
    .header-section {background:#f5f5f0; padding:40px; text-align:center;}
    .logo {width:80px; height:80px; margin-bottom:25px;}
    .greeting {font-size:22px; font-weight:600; color:#2c3e50; margin:0 0 10px;}
    .main-message, .main-message-text {font-size:20px; font-weight:600; color:#2c3e50; margin:0 0 15px; line-height:1.4;}
    .ref-number {font-size:16px; color:#555; margin:0 0 10px;}
    .payment-details-section {background:#fff; padding:30px 40px;}
    .section-title {font-size:18px; font-weight:600; color:#2c3e50; margin:0 0 25px;}
    .payment-card {background:#f8f9fa; border-radius:8px; padding:20px; margin-bottom:25px;}
    .payment-header {display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;}
    .payment-logo {width:40px; height:40px;}
    .payment-date {font-size:14px; color:#666; font-weight:500;}
    .payment-row-table {width:100%; border-collapse:collapse; margin-bottom:8px;}
    .payment-row-table td {padding:8px 0; border-bottom:1px solid #e9ecef; vertical-align:middle;}
    .payment-row-table:last-child td {border-bottom:none;}
    .payment-label {font-size:14px; color:#666; width:40%;}
    .payment-value {font-size:14px; color:#333; font-weight:500; text-align:right; width:60%;}
    .amount-value {font-size:16px; font-weight:600; color:#333;}
    .status-pending {color:#ff9800; font-weight:600;}
    .pay-button {background:#1e88e5; color:#fff; text-decoration:none; padding:12px 40px; border-radius:6px; font-weight:600; font-size:16px; display:inline-block; width:100%; text-align:center; box-sizing:border-box;}
    .pay-button:hover {background:#1976d2;}
    .help-section {padding:0 40px 30px;}
    .help-title {font-size:16px; font-weight:600; color:#2c3e50; margin:0 0 15px;}
    .help-text {font-size:14px; color:#666; line-height:1.6; margin:0;}
    .help-link {color:#1e88e5; text-decoration:none;}
    .help-link:hover {text-decoration:underline;}
    .footer-section {background:#fff; padding:30px 40px; text-align:center; border-top:1px solid #eee;}
    .footer-logo {width:60px; height:60px; margin-bottom:20px;}
    .footer-links {font-size:12px;}
    .footer-links a {color:#1e88e5; text-decoration:none; margin:0 8px;}
    .footer-links a:hover {text-decoration:underline;}
    .payment-request {font-size:16px; color:#555; margin:0 0 15px; line-height:1.5;}
    .highlight-blue {color:#1e88e5; font-weight:600;}
    .support-value-email{color:#1e88e5; font-weight:500;}
    .support-value-mobile{color:lightgreen; font-weight:500;}
    @media (max-width:640px){
      .email-container{margin:20px; max-width:none;}
      .header-section,.payment-details-section,.help-section,.footer-section{padding:30px 20px;}
      .main-message,.main-message-text{font-size:18px;}
      .greeting{font-size:20px;}
      .payment-header{flex-direction:column; align-items:flex-start; gap:10px;}
      .pay-button{padding:14px 20px; font-size:14px;}
      .payment-request,.ref-number{font-size:14px;}
    }
  </style>
</head>
<body>
  <div>
    <div>
      {{#if companyLogoUrl}}<div><img src="{{companyLogoUrl}}" alt="Company Logo"/></div>{{/if}}
      {{#with student}}Hey {{studentName}}{{/with}}
      {{student.institute.instituteName}}
      <p>{{student.institute.instituteName}} institute is requesting you to pay the due payments of Student ID <span>{{student.studentLoginId}}</span></p>
      <p>Ref: <span>{{dueDetails.paymentCollections}}</span></p>
    </div>
    <div>
      Payment Details
      <div>
        <div><img src="logo.svg" alt="School Logo"/><span></span></div>
        <table><tr><td>Total Amount</td><td>₹ {{dueDetails.totalAmountDue}}</td></tr></table>
        <table><tr><td>Due Date:</td><td>{{dueDetails.createdAt}}</td></tr></table>
        <table><tr><td>Institution:</td><td>{{student.institute.instituteName}}</td></tr></table>
        <table><tr><td>Fee Type:</td><td>{{dueDetails.feeTypes}}</td></tr></table>
        <table><tr><td>Payment Status:</td><td>Pending</td></tr></table>
      </div>
      <a href="{{dueDetails.redirectUrl}}">Pay Now</a>
    </div>
    <div>
      Need Assistance?
      {{#with student}}
        <p>If you have any questions, we're just an <a href="mailto:{{student.institute.supportEmail}}">email</a> or <a href="tel:{{student.institute.supportMobile}}">call</a> away.</p>
      {{/with}}
    </div>
    <div>
      {{#if companyLogoUrl}}<div><img src="{{companyLogoUrl}}" alt="Company Logo"/></div>{{/if}}
      <div>
        <span>Support email: {{student.institute.supportEmail}}</span>
        <span>Support Mobile: {{student.institute.supportMobile}}</span>
      </div>
      <div>
        <a href="{{dueDetails.redirectUrl}}">FAQ</a> | <a href="{{dueDetails.redirectUrl}}">Privacy Policy</a> | <a href="{{dueDetails.redirectUrl}}">Terms and Conditions</a>
      </div>
    </div>
  </div>
</body>
</html>
Hi {{ fullName }},

Thank you for signing up for {{ solidAppName }}!

To complete your registration, please use the following One-Time Password (OTP) to verify your email address:

{{ otp }}

This code is valid for 10 minutes. Please do not share this code with anyone.

If you did not attempt to sign up, please disregard this email.

Regards,  
The {{ solidAppName }} Team  

Example : Sending Email Using Template

Below is a code snippet demonstrating how to send an email using the defined email templates via the MailServiceFactory. This example shows how to send an OTP verification email to a user.

// Example: sending an email via the MailServiceFactory

import { Injectable } from '@nestjs/common';
import { MailFactory } from '@solidstarters/solid-core';

@Injectable()
export class MailerExampleService {
  constructor(private readonly mailFactory: MailFactory) {}

  async sendOTPEmail(
    user: {  email: string; fullName?: string; username: string },
    otp: string) {
    const mailService = this.mailFactory.getMailService();

    await mailService.sendEmailUsingTemplate(
      user.email,                      // to
      'otp-verification',      // template key
      {
        solidAppName: process.env.SOLID_APP_NAME,
        fullName: user.fullName ?? user.username,
        otp: otp,
      },
      /* shouldQueue */ true,           // or from config
      /* cc */ null,
      /* bcc */ null,
      /* entityType */ 'user',
      /* entityId */ user.id,
    );
  }
}

Email Templates Metadata Attributes

name (string, required, unique)

Unique name for the email template. It should be in kebab-case format (e.g., example-template-name).

displayName (string, required)

Display name for the email template.

body (string, required)

- In the metadata json, the filename of the email template is specified. The templates are searched for in the `module-metadata/<module-name>/email-templates/` directory of the module.
- The body is then replaced with the content of the email template file. This can include HTML or plain text content. The body can include dynamic placeholders using Handlebars syntax (e.g., `{{placeholderName}}`), as shown in the [Email Template file](#example--email-template-file) above.

Further Reference

Please refer to the Handlebars Documentation for more information on using Handlebars syntax in email templates.

subject (string, required)

Subject line of the email template. It can include dynamic placeholders.

description (string, optional)

A brief description of the email template.

active (boolean, optional)

Indicates whether the email template is active. Defaults to true.

type (string, optional)

Type of the email template (e.g., text, html). Defaults to text.