mod_utils_resend.js

/**
## /utils/resend
The resend module provides a way to send emails to clients/admins, etc.

The resend dependency will be imported dynamically on the condition that a TRANSPORT_EMAIL [sender] is defined the process environment.

The mailer object has two methods. send and batch.
The send function sends one email. And the batch function queues a batch of emails to be sent.

@requires resend
@requires /utils/logger
@requires /utils/languageTemplates
@requires /provider/getFrom

@module /utils/resend
*/

import getFrom from '../provider/getFrom.js';

import languageTemplates from './languageTemplates.js';
import logger from './logger.js';

let resend;

if (xyzEnv.TRANSPORT_PASSWORD && xyzEnv.TRANSPORT_EMAIL) {
  try {
    const { Resend } = await import('resend');
    resend = new Resend(xyzEnv.TRANSPORT_PASSWORD);
  } catch {
    console.error('Error: Missing resend dependency');
  }
}

const mailer = {
  send,
  batch,
};

export default mailer;

/**
@function send
@async

@description
Sends one email via the resend sdk.

@param {Object} params
@property {String} params.to The email recipient.
@property {String} params.template The html that will make up the body of the email.
@property {String} params.language The language to be used.
@property {String} params.host The URL of the instance.
*/
async function send(params) {
  // The resend module is not available.
  if (!resend) return;

  const template = await languageTemplates(params);

  await getBody(template);

  const mailTemplate = {
    from: xyzEnv.TRANSPORT_EMAIL,
    html: template.html
      ? replaceStringParams(template.html, params)
      : undefined,
    sender: xyzEnv.TRANSPORT_EMAIL,
    subject: replaceStringParams(template.subject, params),
    text: template.text
      ? replaceStringParams(template.text, params)
      : undefined,
    to: params.to,
  };

  const { data, error } = await resend.emails.send(mailTemplate);

  if (error) {
    console.error(error);
    return;
  }

  let result = `${data.id}\nFrom: ${xyzEnv.TRANSPORT_EMAIL}\nTo: ${params.to}`;

  logger(result, 'mailer');

  result += `\nBody:\n ${mailTemplate.text?.replace('    ', '')}`;

  logger(result, 'mailer_body');
}

/**
@function batch

@description
Takes an array of emails to queue via the resend sdk. This allows us to trigger 100 emails at once.

@async

@param {Array} emails
@property {String} params.to The email recipient.
@property {String} params.template The html that will make up the body of the email.
@property {String} params.language The language to be used.
@property {String} params.host The URL of the instance.
*/
async function batch(emails) {
  // The resend module is not available.
  if (!resend) return;

  const emailTemplates = [];

  for (const params of Object.values(emails)) {
    const template = await languageTemplates(params);

    await getBody(template);

    const mailTemplate = {
      from: xyzEnv.TRANSPORT_EMAIL,
      html: template.html
        ? replaceStringParams(template.html, params)
        : undefined,
      sender: xyzEnv.TRANSPORT_EMAIL,
      subject: replaceStringParams(template.subject, params),
      text: template.text
        ? replaceStringParams(template.text, params)
        : undefined,
      to: params.to,
    };

    emailTemplates.push(mailTemplate);
  }

  const { error } = await resend.batch.send(emailTemplates);

  if (error) {
    console.error(error);
  }

  let result = `From: ${xyzEnv.TRANSPORT_EMAIL}\nTo: ${emailTemplates.map((template) => template.to)?.join?.(',')}`;

  logger(result, 'mailer');

  result += `\nBody:\n ${emailTemplates?.[0].text?.replace?.('    ', '')}`;

  logger(result, 'mailer_body');
}

/**
@function getBody
@async

@description
Retrieves the body of the text from the provided url/file.

@param {Object} template
@property {String} template.text The url from which to retrieve the text content using {@link module:/provider/getFrom~flyTo}.
@property {String} template.html The url from which to retrieve the html content using {@link module:/provider/getFrom~flyTo}.
*/
async function getBody(template) {
  if (template.text) {
    // Prevent mail template from having text and html
    delete template.html;

    if (Object.hasOwn(getFrom, template.text.split(':')[0])) {
      template.text = await getFrom[template.text.split(':')[0]](template.text);
    }
  }

  if (template.html) {
    if (Object.hasOwn(getFrom, template.html.split(':')[0])) {
      template.html = await getFrom[template.html.split(':')[0]](template.html);
    }
  }
}

/**
@function replaceStringParams

@description
Substitutes supplied params into a supplied string.

@param {String} string
@property {Object} params The url from which to retrieve the text content using {@link module:/provider/getFrom~flyTo}.

@returns {String} The string with substitutions made.
*/
function replaceStringParams(string, params) {
  return string.replaceAll(
    /\$\{(.*?)\}/g,

    // Replace matched params in string values
    (matched) => params[matched.replaceAll(/\$\{|\}/g, '')] || '',
  );
}