Nxt3
Nxt3

Reputation: 2091

Mailgun attachments not showing up

Using Nodejs, I've been able to temporarily store a file that was uploaded in a form to a location. From there, I wanted to use mailgun-js to send an email with that file attached.

I'm able to get the email to display an attachment, but the attachment (in my test case, I'm uploading a regular png file) will be broken. By broken, I mean this:

borked image

I'm not sure what is going on--but I can't seem to find anyone else who had my problem; so I'm sure it's something wrong I've done. I appreciate any and all help.

Edit: If I hardcode the path (i.e. I place an image in a folder and then set the attachment to that path) then it sends the attachment correctly and I can view it no problems.

app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var bodyParser = require('body-parser');
var https = require('https');
var fs = require('fs-extra');
var busboy = require('connect-busboy');
var multer = require('multer');

var app = express();
var upload = multer(({ dest: __dirname + '/tmp/' }));

// view engine setup
app.set('port', (process.env.PORT || 5000));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(favicon(path.join(__dirname,'public','images','favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(busboy());

// Used for accessing webpages without the .html extension!
var publicdir = __dirname + '/public';
app.use(function(req, res, next) {
  if (req.path.indexOf('.') === -1) {
    var file = publicdir + req.path + '.html';
    fs.exists(file, function(exists) {
      if (exists)
      req.url += '.html';
      next();
    });
  }
  else
  next();
});
app.use(express.static(publicdir));
//end .html extension removal

var type = upload.single('file');
app.post('/submit', type, function(req, res, next) {
  console.log("____ Clicked submit ____");

  //whether or not this form will carry an attachment
  var hasAttachment = (req.body.attachment == '') ? false : true;

  //path of file
  var target_path; //actual image
  var tmp_path;

  var name = req.body.name;
  var email = req.body.email;
  var subject = req.body.subject;
  var message = req.body.message;


  //if there is an attachment, handle getting the file
  if (hasAttachment) {
    tmp_path = req.file.path;

    //The original name of the uploaded file stored in the variable target_path
    target_path = __dirname + '/tmp/' + req.file.originalname;

    //A better way to copy the uploaded file.
    var src = fs.createReadStream(tmp_path);
    var dest = fs.createWriteStream(target_path);
    src.pipe(dest);
  }

  //Use validator to sanitize for valid emails
  var validator = require('validator');
  if (!validator.isEmail(email)) {
    console.log("Not a valid email");
    res.end();
    return;
  }

  //Mailgun api stuff
  var api_key = 'redacted';
  var domain = 'redacted';
  var mailgun = require('mailgun-js')({
    apiKey: api_key,
    domain: domain
  });

  //data for mailgun (to, from, name, etc.)
  var data;

  if (hasAttachment) {
    console.log(target_path);

    data = {
      from: email,
      to: '[email protected]',
      subject: subject + ' - ' + name,
      text: message,
      attachment: target_path
    };
  } else {
    data = {
      from: email,
      to: '[email protected]',
      subject: subject + ' - ' + name,
      text: message
    };
  }

  console.log(data);

  mailgun.messages().send(data, function (error, result) {
    if (error) {
      console.error(error);
      return;
    }
  });

  console.log("Sent an email!!!");
  res.redirect('/contact');

  //delete the files after sending the email
  if (hasAttachment) {
    fs.unlink(target_path);
    fs.unlink(tmp_path);
  }
});

var server = app.listen(app.get('port'), function() {
  //nothing
}); //server closing-bracket



/**
* ERROR HANDLING
*/
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    console.error("***ERROR: " + err.message);
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

module.exports = app;

Upvotes: 0

Views: 1306

Answers (1)

chardy
chardy

Reputation: 1253

send() is asynchronous, and you are deleting the file before it's had a chance to finish.

Instead, delete the files in send callback once you are sure it has finished.

Update:

In addition to the above, your file copy is also happening asynchronously and you are attempting to transfer the file before the copy has completed. So make sure you wait for that:

dest.on("close", function(error, result) {
  if (error) {
    console.log("COPY ERROR", error);
    // TODO: respond with some error result

  } else {
    mailgun.messages().send(data, function (error, result) {
      // delete the files
      if (hasAttachment) {
        fs.unlink(target_path);
        fs.unlink(tmp_path);
      }

      if (error) {
         console.error(error);
         // TODO: respond with some error result
      } else {
        console.log("Sent an email!!!");
        res.redirect('/contact');
      }
    });
  }
});

The above change will make your code work.

I presume you are copying the file so that the image will show up properly in the email. (Mailgun infers how to handle the attachment based on its file extension, and Multer's default temp filename doesn't include an extension.)

You can avoid doing the copy altogether by configuring Multer to retain the file extension. https://github.com/expressjs/multer#storage

Upvotes: 1

Related Questions