What am I doing wrong in this custom Server Connect module?

I’m trying to repurpose the nodemailer code from the Mailer action. I created an hjson (rfc822_streamer.hjson) and js (rfc822_streamer.js) file. When I open the Server Action in the browser, I only see the identity.

image

I’m not getting any errors, but I’m also not getting any output. I can see the req in web server logs.

Here’s the hjson code.

{
  type: 'RFC822 Streamer',
  module : 'rfc822_streamer',
  action : 'stream',
  groupTitle : 'Mailer',
  groupIcon : 'fas fa-lg fa-database comp-data',
  title : 'RFC822 Streamer',
  icon : 'fas fa-lg fa-table comp-data',
  dataPickObject: true,
  globalVars: {
    '$_POST' : [
			{name: 'subject', type: 'text'},
			{name: 'importance', type: 'text'},
      {name: 'attachments', type: 'file'},
      {name: 'fromName', type: 'text'},
      {name: 'fromEmail', type: 'text'},
      {name: 'replyTo', type: 'text'},
      {name: 'toName', type: 'text'},
      {name: 'toEmail', type: 'text'},
      {name: 'cc', type: 'text'},
      {name: 'bcc', type: 'text'},
      {name: 'body', type: 'text'},
      {name: 'contentType', type: 'text'},
      {name: 'embedImages', type: 'boolean'}
  		   ]
  },
  properties : [
    {
      group: 'General',
      variables: [
        { name: 'actionSubject', optionName: 'subject', title: 'Subject', 
          type: 'text', required: true, serverDataBindings:'true', defaultValue: ''},
        { name: 'actionImportance', optionName: 'importance', title: 'Importance',
          type: 'text', serverDataBindings:'true', defaultValue: ''},
        { name: 'actionAttachments', optionName: 'attachments', title: 'Attachments',
          type: 'file', serverDataBindings:'true', defaultValue: ''}
      ]
    },
    {
      group: 'Sender',
      variables: [
        { name: 'actionName', optionName: 'fromName', title: 'Name', 
          type: 'text', required: true, serverDataBindings:'true', defaultValue: ''},
        { name: 'actionEmail', optionName: 'fromEmail', title: 'Email', 
          type: 'text', required: true, serverDataBindings:'true', defaultValue: ''},
        { name: 'actionReplyTo', optionName: 'replyTo', title: 'Reply To', 
          type: 'text', required: false, serverDataBindings:'true', defaultValue: ''}
      ]
    },
    {
      group: 'Recipient(s)',
      variables: [
        { name: 'actionToName', optionName: 'toName', title: 'To Name', 
          type: 'text', required: true, serverDataBindings:'true', defaultValue: ''},
        { name: 'actionToEmail', optionName: 'toEmail', title: 'To Email', 
          type: 'text', required: true, serverDataBindings:'true', defaultValue: ''},
        { name: 'actionCc', optionName: 'cc', title: 'Cc', 
          type: 'text', required: false, serverDataBindings:'true', defaultValue: ''},
        { name: 'actionBcc', optionName: 'bcc', title: 'Bcc', 
          type: 'text', required: false, serverDataBindings:'true', defaultValue: ''}
      ]
    },
    {
      group: 'Mail Body',
      variables: [
        { name: 'actionContent', optionName: 'body', title: 'Content', 
          type: 'textarea', required: true, serverDataBindings:'true', defaultValue: ''},
        { name: 'actionFormat', optionName: 'contentType', title: 'Format',
          type: 'text', serverDataBindings:'true', defaultValue: ''},
        { name: 'actionEmbedImages', optionName: 'embedImages', title: 'Embed Images',
          type: 'boolean', serverDataBindings:'true', defaultValue: false},
        { name: 'output', optionName: 'output', title: 'Output', 
          type: 'boolean', defaultValue: false
        }
      ]
    }
  ]
}

Here’s the js code.

const fs = require('fs-extra');
const { getFilesArray, toSystemPath } = require('../../../lib/core/path');
const { basename, posix } = require('path');
const { v4: uuidv4 } = require('uuid');
const IMPORTANCE = { 0: 'low', 1: 'normal', 2: 'high' };

exports.stream = async function (options) {

    //let setup = this.getMailer(this.parseOptional(options.instance, 'string', 'system'));
    let subject = this.parseRequired(options.subject, 'string', 'mail.send: subject is required.');
    let fromEmail = this.parseRequired(options.fromEmail, 'string', 'mail.send: fromEmail is required.');
    let fromName = this.parseOptional(options.fromName, 'string', '');
    let toEmail = this.parseRequired(options.toEmail, 'string', 'mail.send: toEmail is required.');
    let toName = this.parseOptional(options.toName, 'string', '');
    let replyTo = this.parseOptional(options.replyTo, 'string', '');
    let cc = this.parseOptional(options.cc, 'string', '');
    let bcc = this.parseOptional(options.bcc, 'string', '');
    //let source = this.parseOptional(options.source, 'string', 'static'); // static, file
    let contentType = this.parseOptional(options.contentType, 'string', 'text'); // text / html
    let body = this.parseOptional(options.body, 'string', '');
    // let bodyFile = this.parseOptional(options.bodyFile, 'string', '');
    let embedImages = this.parseOptional(options.embedImages, 'boolean', false);
    let priority = IMPORTANCE[this.parseOptional(options.importance, 'number', 1)];
    let attachments = this.parseOptional(options.attachments, '*', []); // "/file.ext" / ["/file.ext"] / {path:"/file.ext"} / [{path:"/file.ext"}]

    let from = fromName ? `"${fromName}" <${fromEmail}>` : fromEmail;
    let to = toName ? `"${toName}" <${toEmail}>` : toEmail;
    let text = body;
    let html = null;

    // if (source == 'file') {
    //     body = this.parse(await fs.readFile(toSystemPath(bodyFile), 'utf8'));
    // }

    if (attachments) {
        attachments = getFilesArray(attachments).map((path) => ({ filename: basename(path), path }));
    }

    if (contentType == 'html') {
        html = body;

        if (embedImages) {
            let cid = {};

            html = html.replace(/(?:"|')([^"']+\.(jpg|png|gif))(?:"|')/gi, (m, url) => {
                let path = toSystemPath(url);

                if (fs.existsSync(path)) {
                    if (!cid[path]) {
                        cid[path] = uuidv4();
                        attachments.push({
                            filename: basename(path),
                            path: path,
                            cid: cid[path]
                        });
                    }

                    return `"cid:${cid[path]}"`;
                } else {
                    console.warn(`${path} not found`);
                }

                return `"${url}"`;
            });
        }

        if (this.req.get) { // we can only do this if we have a request to get our hostname
            const hasProxy = !!this.req.get('x-forwarded-host');
            const host = hasProxy ? `${this.req.protocol}://${this.req.hostname}` : this.req.get('host');

            html = html.replace(/(href|src)(?:\s*=\s*)(?:"|')([^"']+)(?:"|')/gi, (m, attr, url) => {
                if (!url.includes(':')) {
                    url = posix.join(host, url);
                }

                return `${attr}="${url}"`;
            });
        }
    }

    const nodemailer = require('nodemailer');
    let transport = nodemailer.createTransport({ streamTransport: true });
    return transport.sendMail({ from, to, cc, bcc, replyTo, subject, html, text, priority, attachments });
};

Not entirely sure about this one, but I think you need to have a property which has name as “actionName” & optionName as “name”.
This serves as the key for the output value in final output JSON.

1 Like

Thanks @sid. I already have that in the hjson, unless I wrote it incorrectly.

I forgot I ran across this issue before in this post.

I needed a name variable in the properties of the hjson.

      {
        name: "name",
        optionName: "name",
        title: "Name",
        type: "text",
        required: true,
        defaultValue: ""
      },

That’s output. I was talking about name… which it seems you have figured out.

doh! Too early in the morning. :slight_smile: Thanks, @sid!

1 Like