wanglian/workbase-server

View on GitHub
imports/mailgun/server/outgoing/mailgun.js

Summary

Maintainability
A
1 hr
Test Coverage
const request = require('request');
const MailgunClient = require('mailgun-js');
const moment = require('moment');

Mailgun = {
  setup(key, domain) {
    /* Check settings existence */
    /* This is the best practice for app security */
    if (!key || !domain) {
      throw new Meteor.Error(401, 'Missing Mailgun settings');
    }
    Mailgun.client = MailgunClient({apiKey: key, domain});
  },
  validate(domain) {
    if (!Mailgun.client) {
      return false;
    }
    return new Promise((resolve, reject) => {
      Mailgun.client.domains(domain).info((err, res) => {
        if (err) {
          // 401 - Invalid private key
          // 404 - Domain not found
          resolve(err.statusCode);
        } else {
          resolve(true);
        }
      });
    });
  }
};

Meteor.startup(function() {
  let instance = Instance.get();
  let modules = instance && instance.modules;
  let email = modules && modules.email;
  email && email.type === 'mailgun' && Mailgun.setup(email.mailgun.key, instance.domain);
});

const buildMailgunAttachment = (file) => {
  return new Mailgun.client.Attachment({
    data:        request({encoding: null, url: Files.link(file)}),
    filename:    file.name,
    contentType: file.type,
    knownLength: file.size
  });
};

const buildSubject = (thread, message) => {
  let subject = thread.subject;
  if (thread.category === 'Chat') {
    let parentMessage = message.parent();
    let user = message.user();
    subject = parentMessage && parentMessage.email && parentMessage.email.subject || I18n.getFixedT(user.profile.language)("email_message_from_who", {user: user.name()});
  }
  return subject;
};

const buildTextContent = (message) => {
  let content = message.content;
  let p = message.parent();
  if (p) {
    content = content + `\r\n\r\n> ${moment(message.createdAt).format('YYYY-MM-DD HH:mm')}, ${_.escape(p.user().address())}: \r\n` + p.summary.replace(/^\s*[\r\n]/gm, ' ');
  }
  return content;
};

const buildHTMLContent = (message) => {
  let content = Markdown(message.content);
  let type = 'html';

  switch(message.contentType) {
  case 'image':
    let image = message.image;
    content = content + `<br/><img src="cid:${image.name}.${image.extension}"/>`;
  default:
    let p = message.parent();
    if (p) {
      content = content + `<br/>${moment(message.createdAt).format('YYYY-MM-DD HH:mm')}, ${_.escape(p.user().address())}: <blockquote style="padding:0 10px;margin:0;border-left:2px solid #ddd;">${p.content}</blockquote>`;
    }
  }

  let user = message.user();
  return content + '<br/><br/>' + Markdown(user.signature());
};

Mailgun.send = (message) => {
  Messages.update(message._id, {$set: {"email.state": 'sending'}});

  let threadId = message.threadId;
  let thread = Threads.findOne(threadId);
  let contacts = thread.externalMembers();
  contacts = _.reject(contacts, (c) => c.noreply());

  if (!_.isEmpty(contacts)) {
    let user = message.user();

    let params = {
      from:    (message.email && message.email.from) || user.address(),
      to:      contacts.map((c) => c.address()),
      subject: buildSubject(thread, message)
    };

    switch (message.contentType) {
    case 'image':
      let image = message.image();
      _.extend(params, {
        text: message.content,
        html: buildHTMLContent(message),
        inline: buildMailgunAttachment(image),
        'v:MessageType': 'image'
      });
      break;
    case 'html':
      _.extend(params, {
        html: buildHTMLContent(message)
      });
      break;
    default:
      _.extend(params, {
        text: buildTextContent(message),
        html: buildHTMLContent(message),
        'v:MessageType': 'text'
      });
    }

    // attachments
    let files = message.files();
    if (files && files.count() > 0) {
      _.extend(params, {
        attachment: files.map((file) => buildMailgunAttachment(file))
      });
    }

    // "h:Reply-To" last message
    let last = Messages.findOne({threadId, emailId: {$exists: true}}, {sort: {createdAt: -1}});
    let replyTo = last && last.emailId;
    if (replyTo) {
      _.extend(params, {"h:In-Reply-To": replyTo});
    }

    // "h:References"

    Mailgun.client.messages().send(params, Meteor.bindEnvironment((err, result) => {
      if (err) {
        Messages.update(message._id, {$set: {
          "email.state": 'failed'
        }});
        throw new Error(err);
      } else {
        Messages.update(message._id, {$set: {
          internal:        false,
          emailId:         result["id"],
          "email.state":   "ok",
          "email.subject": params.subject,
          "email.from":    params.from,
          "email.to":      params.to.join(", ")
        }});
      }
    }));
  }
};