orliesaurus/nodemailer-mailgun-transport

Uses /messages.mime endpoint

Opened this issue · 4 comments

It seems like a more robust approach would be to use Mailgun's /messages.mime endpoint to send raw emails. This is what the nodemailer-ses-transport does, for example: nodemailer already gives you the mime-formatted email, and all you need to do is pipe that to Mailgun.

Would you like to send a pull request with that feature please? It sounds like a nice thing to have

here's an implementation of just that (coffeescript)

axios = require 'axios'
FormData = require 'form-data'

module.exports = class MailgunMIMETransport
  name: "MailgunMIMETransport"
  version: "1.0.0"

  constructor: (@options={}) ->

  send: (mail, callback) =>
    mailStream = mail.message.createReadStream()
    this.readStream mailStream, (err, raw) =>
      if err?
        return typeof callback is 'function' and callback(err)
      this.handleMessage mail, raw, callback

  readStream: (mailStream, callback) ->
    chunks = []
    totalLength = 0

    mailStream.on 'data', (chunk)  ->
      chunks.push chunk
      totalLength += chunk.length

    mailStream.on 'end', ->
      callback null, Buffer.concat(chunks, totalLength).toString()

  handleMessage: (mail, raw, callback) ->
    envelope = mail.message.getEnvelope()
    form = new FormData()
    form.append "to", envelope.to.join ','
    form.append "message", raw, 'message.eml'

    requestOptions =
      baseURL: "https://api.mailgun.net/v3/#{@options.domain}"
      headers: form.getHeaders()
      auth:
        username: 'api'
        password: @options.apiKey
    axios.post '/messages.mime', form, requestOptions
    .then (result) ->
      response = result.data
      info =
        envelope: envelope
        response: response.message
        messageId: response.id
      if typeof callback is 'function' then callback(null, info) else info
    .catch (err) ->
      return typeof callback is 'function' and callback(err)

Here's a JS version:

const nodemailer = require('nodemailer');
const mailgun = require('mailgun-js')();

class MailgunTransport {
    constructor (options) {
        this.name = 'Mailgun';
        this.version = '1.0.0';

        this.mailgun = mailgun(options);
    }

    send (mail, callback) {
        const envelope = mail.data.envelope || mail.message.getEnvelope();
        const stream = mail.message.createReadStream();
        stream.once('error', callback);

        const chunks = [];
        let chunkLength = 0;
        stream.on('readable', () => {
            let chunk;
            while ((chunk = stream.read()) !== null) {
                chunks.push(chunk);
                chunkLength += chunk.length;
            }
        });
        stream.on('end', () => {
            const mimeMessage = Buffer.concat(chunks, chunkLength);
            this.mailgun.messages().sendMime({
                to: envelope.to.join(','), // envelope.to contains the message's To, CC, and BCC
                message: mimeMessage.toString('ascii') // nodemailer encodes with quoted-printable, so it's already ASCII
            }, (err, body) => {
                if (err) {
                    return callback(err);
                }

                return callback(null, {
                    envelope: {
                        from: envelope.from,
                        to: envelope.to
                    },
                    response: body,
                    messageId: body.id
                });
            });
        });
    }
}

Happy to throw up a PR, but this would potentially be a breaking change.

Mmmh I don't know about this one ultimately - I don't want to upset all the people - unless it was properly documented and unit tests are added, it'll have to wait :)