sendgrid-nodejs icon indicating copy to clipboard operation
sendgrid-nodejs copied to clipboard

Example on using inbound-mail-parser to parse incoming emails

Open machinshin opened this issue 8 years ago • 40 comments

Issue Summary

A summary of the issue and the environment in which it occurs. If suitable, include the steps required to reproduce the bug. Please feel free to include screenshots, screencasts, code examples.

I am unable to figure out how to use the @sendgrid/inbound-mail-parser package to parse inbound emails. need some example code

Steps to Reproduce

Setup a parse route in sendgrid (linked here: https://www.dropbox.com/s/cb6lwrmon9qwjzq/Screenshot%202017-10-12%2016.03.49.png?dl=0 ) this is the contents of an example email to the route in ngrok: https://gist.github.com/machinshin/e38cf7d20ec6319edcfda63ff7aca594

I have connected the parseroute to my webhook like so:

router.post('/api/email/parseroute', (req, res, next) => {
// have also tried:
router.post('/api/email/parseroute', bodyParser.raw(), (req, res, next) => {

    console.log(`-------------`)
    console.log(req.body)
    console.log(`-------------`)
    console.log(req.rawBody)
    console.log(`-------------`)

}

{}

undefined

As you can see, req.body is empty, and req.rawBody is 'undefined' Thus, I am not clear on how to get access to the raw email data, nor what to do with that data afterwards and how to instantiate the inbound-mail-parser

Any other information you want to share that is relevant to the issue being reported. Especially, why do you consider this to be a bug? What do you expect to happen instead?

Technical details:

  • sendgrid-nodejs Version: 6.1.4 (latest commit: 4c6d1cc )
  • Node.js Version: 4.1.2 "@sendgrid/inbound-mail-parser": "^6.1.4", "@sendgrid/mail": "^6.1.4",

machinshin avatar Oct 12 '17 23:10 machinshin

Hello @machinshin,

Thanks for taking the time to report this. I have added this to our backlog for further investigation.

thinkingserious avatar Oct 12 '17 23:10 thinkingserious

Any update on example? I really need to use this parser but I don't know how to.

ZeroCho avatar Jan 24 '18 15:01 ZeroCho

Hi @ZeroCho,

We do have a package here, but unfortunately there is no good usage documentation. For that, you might want to check out the Python Inbound Parser.

With Best Regards,

Elmer

thinkingserious avatar Jan 26 '18 02:01 thinkingserious

SendGrid sends their JSON inbound parse data as form/multi-part data. This will not be accessed immediately by req.body as body-parser doesn't deal with that format. I would use multer as an alternative.

shishido92 avatar May 01 '18 16:05 shishido92

@shishido92 Could you expand on a good example of how to do it. It looks like the documentation for any parsing inbound emails with nodejs is just nonexistent. Thanks!

stephenfjohnson avatar Sep 03 '18 17:09 stephenfjohnson

@stephenfjohnson @machinshin If you are using expressjs this might help

app.use(express.json({limit: '10mb'}));
app.use(express.urlencoded({
  extended: true,
  type: "multipart/form-data",
  limit: '10mb'
}));
app.post('/endpoint',  function (req, res, next) {
  // req.body contains the text fields
  console.log(req.body);
  console.log('body' + req.body);
  res.status(200).json('ok');
})

Doing this will give you the contents of the request in the request body. This ignores attachments though.

satyajeetjadhav avatar Oct 26 '18 12:10 satyajeetjadhav

@nathfreder We use thumbs-up reactions on the issue summary as part of our prioritization. Please give a 👍 at the top.

childish-sambino avatar Jun 18 '19 18:06 childish-sambino

@childish-sambino , @satyajeetjadhav 👍

Later late than never ;) Thanks for the update, closing this issue

machinshin avatar Jun 24 '19 22:06 machinshin

I know this has been closed but I've just spotted this issue and I have express with inbound-mail-parser working. Main thing I needed to do was to use the multer library to handle the multipart/form-data input and then use the keyValues() method of the @sendgrid/inbound-mail-parser library like below

require in multer and Sendgrid:

const multer = require('multer'); const upload = multer(); const mailParse = require('@sendgrid/inbound-mail-parser');

on my router declaration for the POST method use the upload.none() method as I was just getting text (No attachments):

router.post('/mail', upload.none(), function(req, res, next)

Within that POST definition I then use the the keyValues() method to get the relevant parts of the email:

try {
const config = {keys: ['to', 'from', 'subject', 'text',]}; const parsing = new mailParse(config, req.body); let response = parsing.keyValues(); let to = response.to; let from = response.from; let subject = response.subject; let messageBody = response.text console.log('This is the subject from the mail: ', subject); }

Hope that is of some use.

jhorsfield-tw avatar Sep 08 '19 12:09 jhorsfield-tw

Hi all, can someone share an example how to parse attachments? i am using same configs, but attachment come in unreadeble string


const app = express();
app.use(cors);
app.use(busboy());
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies.
app.use(bodyParser.json({limit: '5mb'}));

router.post('', upload.any(),function(req, res) {
  // console.log(req.body);
  // console.log(req.files);
  // res.send(req.body);

  const config = {keys: ['to', 'from', 'subject', 'text','attachments']};
  const parsing = new mailParse(config, req.body);
  let response = parsing.keyValues();
  let to = response.to;
  let from = response.from;
  let subject = response.subject;
  let messageBody = response.text
  console.log('This is the subject from the mail: ', response);
  res.send(200);
});

simonwebdev000000001 avatar Sep 09 '19 16:09 simonwebdev000000001

The code posted by @jhorsfield-tw was very helpful since @sendgrid/inbound-mail-parser has no documentation, however it did not quite work for me. The constructor for mailParse takes request as a parameter not the request body.

const parsing = new mailParse(config, req);

jtmilne avatar Oct 12 '19 03:10 jtmilne

Hi @ZeroCho,

We do have a package here, but unfortunately there is no good usage documentation. For that, you might want to check out the Python Inbound Parser.

With Best Regards,

Elmer

So you should write documentation then.

Rashe avatar Nov 06 '19 09:11 Rashe

How is this still undocumented

sgammon avatar Nov 25 '19 21:11 sgammon

still can't figure it out with nodejs express...

cpeker avatar Mar 09 '20 19:03 cpeker

Re-opening as there is still no example for this in the docs.

Pull requests to add this feature are welcome and will be reviewed based on priority, but Twilio SendGrid is not actively building new functionality for the library.

childish-sambino avatar Mar 11 '20 16:03 childish-sambino

Trying to receive attachments but still no luck with that...

matheustavaresdev avatar Mar 29 '20 08:03 matheustavaresdev

Trying to receive attachments but still no luck with that...

Use RAW

Rashe avatar Mar 29 '20 08:03 Rashe

const config = {
          keys: ['to', 'from', 'subject', 'text', 'attachments']
        };
        const parsedMail = new parse(config, req); // req is https.Request type

        parsedMail.getRawEmail((rawMail: any) => {
           console.log(rawMail); // returns null or {}
       }

         parsedMail.attachments((attachmentData: Attachment[]) => {
              console.log('attachments1:'); // returns [ ]
              console.log(attachmentData);
              console.log(JSON.stringify(attachmentData));
            });

I'm trying that but with no luck... it returns an empty object and when I do .attachments() I also get an empty array.

matheustavaresdev avatar Mar 29 '20 08:03 matheustavaresdev

I am using firebase cloud functions, I have not had any success using the inbound mail parser and I am looking forward to some documentation that can help me use the library...

But using busboy (and deselecting "raw" in Sendgrid settings) is working quite well, except for one issue that lead me to try to use the mailparser...

Sendgrid lists the different encodings in the "charsets" field like this: An email from Gmail: {"to":"UTF-8","html":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"UTF-8"} An email from outlook: {"to":"UTF-8","html":"iso-8859-1","subject":"UTF-8","from":"UTF-8","text":"iso-8859-1"}

When the emails contains international characters (like ÆØÅ) the result is that the text and html fields contains questionmarks instead of the actual nordic characters.

If I change the defCharset on busboy to Windows-1252 the text and html values will display correctly, but the other fields will display wrongly.

Will this be an issue also on the mail parser, or does the mail parser handle different encodings on the fields?

My code for parsing using busboy for anyone interested: (If anyone can tell me how I solve the issue above using busboy I would be grateful..)

exports.sendgridparse = functions.https.onRequest((req, res) => { const path = require('path'); const os = require('os'); const fs = require('fs'); const Busboy = require('busboy');

if (req.method !== 'POST') {
    // Return a "method not allowed" error
    return res.status(405).end();
}
const busboy = new Busboy({ headers: req.headers });    
const fields = {};
busboy.on('field', function(fieldname, value, fieldnameTruncated, valTruncated, encoding, mimetype){
    console.log('Busboy fild [' + fieldname + ']: value: ' + val);
fields[fieldname] = value;
});

let imageToBeUploaded = {}; let imagestobeuploaded = []; let imageFileName;

busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { console.log(fieldname, file, filename, encoding, mimetype);

file.on('data', function(data) { console.log('File [' + fieldname + '] got ' + data.length + ' bytes'); }); file.on('end', function() { console.log('File [' + fieldname + '] Finished'); });

const imageExtension = filename.split('.')[filename.split('.').length - 1]; imageFileName = ${Math.round(Math.random() * 1000000000000).toString()}.${imageExtension}; const filepath = path.join(os.tmpdir(), imageFileName); const url = https://firebasestorage.googleapis.com/v0/b/${environment.firebase.storageBucket}/o/${imageFileName}?alt=media; imageToBeUploaded = { filepath, mimetype, imageFileName, imageExtension, url }; imagestobeuploaded.push(imageToBeUploaded); file.pipe(fs.createWriteStream(filepath)); });

busboy.on('finish', async () => { //all fields are available (imagestobeuploaded and fields) and can be saved to db res.send(200); }); busboy.end(req.rawBody); });

hhetland avatar Apr 02 '20 07:04 hhetland

@hhetland so, were you able to get the files using busboy? I'm also using it to get the other data from the email, but wasn't able to get the files... Does getting the files work for you?

matheustavaresdev avatar Apr 02 '20 07:04 matheustavaresdev

@matheustavaresdev - Yes, I am getting the files using busboy.

First they are saved to a tmp folder in busboy.on('file'), and then using the information in the variable "imagestobeuploaded" I'm saving it to a storage bucket in firebase in busboy.on('finish').

hhetland avatar Apr 02 '20 07:04 hhetland

awesome thanks!!!!!!!!!!!!

matheustavaresdev avatar Apr 02 '20 22:04 matheustavaresdev

April 2020, still no official docs, right?

I know this has been closed but I've just spotted this issue and I have express with inbound-mail-parser working. Main thing I needed to do was to use the multer library to handle the multipart/form-data input and then use the keyValues() method of the @sendgrid/inbound-mail-parser library like below

require in multer and Sendgrid:

const multer = require('multer'); const upload = multer(); const mailParse = require('@sendgrid/inbound-mail-parser');

on my router declaration for the POST method use the upload.none() method as I was just getting text (No attachments):

router.post('/mail', upload.none(), function(req, res, next)

Within that POST definition I then use the the keyValues() method to get the relevant parts of the email:

try { const config = {keys: ['to', 'from', 'subject', 'text',]}; const parsing = new mailParse(config, req.body); let response = parsing.keyValues(); let to = response.to; let from = response.from; let subject = response.subject; let messageBody = response.text console.log('This is the subject from the mail: ', subject); }

Hope that is of some use.

What I find is that the request.body is in form-data and see an error: [Nest] 24687 - 05/01/2020, 1:29:51 PM [ExceptionsHandler] Reduce of empty array with no initial value TypeError: Reduce of empty array with no initial value at Array.reduce ()

The code: keyValues() { return this.keys .filter(key => this.payload[key]) .map(key => ({ [key]: this.payload[key] })) .reduce((keyValues, keyPayload) => Object.assign(keyValues, keyPayload)); }

returns an error as this.payload isn't a structured data but is multi-part form data.

shwetabhandare avatar May 01 '20 19:05 shwetabhandare

I wasn't able to get this working with nest-js. The webhook data I receive in NestJS controller is multi-form data. I am unsure how to use Multer in that case.

shwetabhandare avatar May 01 '20 22:05 shwetabhandare

outer.post('/mail', upload.none(), function(req, res, next)

I tried doing this:

    async use(req: Request, res: Response, next: Function) {
        console.log("before: ", req.body)
        var upload = multer();
        await new Promise((resolve, reject) => {
            upload.none()(req, res, err => {
                if (err) {
                    console.log(err)
                    reject(err)
                } else {
                    resolve()
                    console.log("request: ", req.body)
                }
            });
        });
        console.log("after: ", req.body)
        next();
    }
}```

However, the req.body returns:
```request:  [Object: null prototype] {}```

The before prints out multiform data. 

shwetabhandare avatar May 02 '20 21:05 shwetabhandare

If you are on serverless (google cloud functions or firebase)

this might help: https://stackoverflow.com/questions/47242340/how-to-perform-an-http-file-upload-using-express-on-cloud-functions-for-firebase/48648805#48648805

This using busboy: https://stackoverflow.com/questions/51484307/how-to-parse-multipart-form-data-on-firebase-cloud-functions

Use Formidable for parsing multipart on serverless: https://github.com/DanisHack/formidable-serverless

Or this: https://github.com/cristovao-trevisan/express-multipart-file-parser

DanisHack avatar May 24 '20 22:05 DanisHack

Here are the changes I had to make so I could use multer:

app.use(express.json()) app.use(express.urlencoded({ extended: true })); const upload = multer(); app.use(upload.none());

Once I did this, I was able to use multer to parse the webhook data into a structured key, value pair.

On Sun, May 24, 2020 at 4:51 PM Danish Mohd [email protected] wrote:

If you are on serverless (google cloud functions or firebase) this might help: https://stackoverflow.com/questions/47242340/how-to-perform-an-http-file-upload-using-express-on-cloud-functions-for-firebase/48648805#48648805

Use Formidable for parsing multipart on serverless: https://github.com/DanisHack/formidable-serverless

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/sendgrid/sendgrid-nodejs/issues/482#issuecomment-633312295, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB2ATHHFKBRZZDGGJ2D7SOLRTGQHNANCNFSM4D67CVZQ .

--

Shweta Bhandare, PhD | Principal Software Engineer

[email protected]

https://hicleo.com/ hicleo.com | Linkedin https://www.linkedin.com/company/hicleo/ | Twitter https://twitter.com/hi_cleo/ | Instagram https://www.instagram.com/hicleo.co/ | Facebook https://www.facebook.com/hicleolabs

--

This message may contain confidential, proprietary, or protected information.  If you are not the intended recipient, you may not review, copy, or distribute this message. If you received this message in error, please notify the sender by reply email and delete this message.

shwetabhandare avatar May 25 '20 13:05 shwetabhandare

We spent a lot of time trying to get this to work in Google Cloud Functions with Busboy and couldn't make it happen no matter what tutorial or pointers we followed.

So I'll leave this here for the next person in a similar situation where you have a bit of flexibility: use the Python Inbound Parser. It was pretty much a copy/paste job from the code they provide and took under an hour to get it running.

I know this doesn't solve the issue here, but I hope it can save someone else hours of headbanging.

ItIsJustChad avatar Jun 03 '20 13:06 ItIsJustChad

I'm glad I found this ticket... I was going crazy trying to find some documentation on this, and somehow there is none.

I'm actually baffled. I just assumed there would be standard documentation - especially because while this is open source it is backed by SendGrid (and ultimately Twilio, which has fantastic documentation).

For something as simple as this, you'd imagine there would at least be an example.

Nonetheless, for anyone who comes across this...

I recommend using MailParser instead.

I'm pretty sure this uses it under the hood, but at least MailParser has loads of documentation. Just make sure that in your inbound parse settings you have POST the raw, full MIME message checked as shown below (wow, an example).

Example

Reading through the MailParser docs, I got this going in minutes:

import { Request, Response } from "express";
import { simpleParser } from "mailparser";

export const inboundEmails = async (req: Request, res: Response) => {
  const parsedEmail = await simpleParser(req.body.email);

  console.log("subject", parsedEmail.subject);
  console.log("from", parsedEmail.from);
  console.log("to", parsedEmail.to);
  console.log("cc", parsedEmail.cc);
  console.log("bcc", parsedEmail.bcc);
  console.log("date", parsedEmail.date);
  console.log("messageId", parsedEmail.messageId);
  console.log("inReplyTo", parsedEmail.inReplyTo);
  console.log("replyTo", parsedEmail.replyTo);
  console.log("references", parsedEmail.references);
  console.log("html", parsedEmail.html);
  console.log("text", parsedEmail.text);
  console.log("textAsHtml", parsedEmail.textAsHtml);
  console.log("attachments", parsedEmail.attachments);

  res.sendStatus(200);
};

All the data is as you would like it.

I also use the Multer middleware to parse the multipart/form-data that SendGrid POSTs to my endpoint.

It's as simple as multer().any() within your middleware flow.

Cheers

dilizarov avatar Jun 10 '20 06:06 dilizarov