GMail API Replying to Email Thread Using NodeJS - node.js

Dear All: I am sure many of you have discussed above topic multiple times after I am going through all the example and references I have managed to write the code to reply to same email ThreadID. But Unfortunately while I am responding to same ThreadID emails it's going as new email. I have attached my complete NodeJS Code help me to review and let me know where should I have to make the changes.
const {google} = require('googleapis');
const mailComposer = require('nodemailer/lib/mail-composer');
var program_name = process.argv[0]; //value will be "node"
var script_path = process.argv[1]; //value will be "yourscript.js"
var Sender_Email = process.argv[2]; //value will be "Sender Email"
var Receiver_Email = process.argv[3]; //value will be "Email To"
//var CC_Email = process.argv[4]; //value will be "Email Cc"
var Email_Subject = process.argv[4]; //value will be "Email Subject"
var Email_Template = process.argv[5]; //value will be "Email Template"
var ThreadID = process.argv[6]; //Path to attach the file
var Dec_Message_ID = process.argv[7]; //Encoded messageid
var FileAttachment = process.argv[8]; //Path to attach the file
var dateFormat = require('dateformat');
var day=dateFormat(new Date(), "mmm dd, yyyy HH:MM tt");
class CreateMail{
constructor(auth, to, cc, sub, body, task, attachmentSrc = [FileAttachment]){
this.me = Sender_Email;
this.task = task;
this.auth = auth;
this.to = Receiver_Email;
//this.cc = CC_Email;
this.sub = Email_Subject;
var fs = require('fs');
this.body = fs.readFileSync(Email_Template,{encoding:'utf-8'});
this.gmail = google.gmail({version: 'v1', auth});
this.attachment = attachmentSrc;
}
//Creates the mail body and encodes it to base64 format.
makeBody(){
if(this.attachment.length>0){
var arr = [];
for(var i=0;i<this.attachment.length;i++){
arr[i] = {
path: this.attachment[i],
encoding: 'base64'
}
}
}
let mail;
//Mail Body is created.
if(this.attachment.length>0){
mail = new mailComposer({
from: "Arthanareeswaran Chandrasekaran <arthaaadhi#visha.page>",
//sender: this.me,
to: this.to,
//cc: this.cc,
replyTo: this.to,
inReplyTo: "<CAO29sXBTxmE8M=xyTkdFfsrxB_Mdr5e6N6vXiijwTY9rn1kzpQ#mail.gmail.com>",
references: "<CAF7UyHwMrUvy-ZLNyRfjDmX876EKi5T-oc8E_tXy2PwO19dZ_Q#mail.gmail.com> <CAO29sXBH_B0yG4G2p6tdW1uk_tq9qFXmc01CPO5HJopkvMbU4Q#mail.gmail.com> <CAO29sXCcHv4LQSumjht_5zHEYvSzjfYkGr+yCEHfjwnqRvt0=Q#mail.gmail.com> <CAO29sXCPAxzWG0dC-TKEi4cR3xM8hbHhSJQ0ZAhbXBjsp503oA#mail.gmail.com> <CAO29sXA2mpqx6qbEeB5ke_6kUTrwXsqMD8ku0Aq3E_R07YzCLg#mail.gmail.com> <CAO29sXBTxmE8M=xyTkdFfsrxB_Mdr5e6N6vXiijwTY9rn1kzpQ#mail.gmail.com>",
subject: this.sub,
html: this.body,
textEncoding: "base64",
attachments: arr
});
}
else{
mail = new mailComposer({
to: this.to,
cc: this.cc,
html: this.body,
subject: this.sub,
textEncoding: "base64"
});
}
//Compiles and encodes the mail.
mail.compile().build((err, msg) => {
if (err){
return console.log('Error compiling email ' + error);
}
const encodedMessage = Buffer.from(msg)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
if(this.task === 'mail'){
this.sendMail(encodedMessage);
}
else{
this.saveDraft(encodedMessage);
}
});
}
//Send the message to specified receiver.
sendMail(encodedMessage){
this.gmail.users.messages.send({
userId: this.me,
resource: {
raw: encodedMessage,
threadId: ThreadID
}
}, (err, result) => {
if(err){
return console.log('GMail API - The API returned an error: ' + err);
}
console.log("GMail API Sending Email Reply from server:", result.data);
});
}
//Saves the draft.
saveDraft(encodedMessage){
this.gmail.users.drafts.create({
'userId': this.me,
'resource': {
'message': {
'raw': encodedMessage,
threadId: ThreadID
}
}
})
}
//Deletes the draft.
deleteDraft(id){
this.attachment.gmail.users.drafts.delete({
id: id,
userId: this.me
});
}
}
module.exports = CreateMail;
Thanks for your help...

I suggest you update your code to this and check that the References, In-Reply-To and Subjects headers match:
function replyToMessage(auth) {
const gmail = google.gmail({
version: 'v1',
auth
});
const messages = [
'From: NAME <email#email.com>',
'To: NAME <email#email.com>',
'References: <REFERENCE1> <REFERENCE2>',
'In-Reply-To: <IN_REPLY_TO>',
'Content-Type: text/html; charset=utf-8',
'MIME-Version: 1.0',
'Subject: Re: SUBJECT',
'',
'BODY_OF_THE_REPLY',
'',
];
const message = messages.join('\n');
const encodedMessage = Buffer.from(message)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
gmail.users.messages.send({
auth: auth,
userId: 'me',
resource: {
raw: encodedMessage,
threadId: 'THREAD_ID'
}
});
}
Also please bear in mind that in order to see the replies accordingly, you will have to turn on the Conversation View from Gmail Settings.
According to the documentation:
You can choose whether replies to emails are grouped in conversations, or if each email shows up in your inbox separately.
Hence, if this setting is not turned on, the email will show up separately in your inbox.
Reference
Gmail Help.

Related

DocuSign createEnvelope Request Failed with 404 Error

I have been trying to create an envelope from a template using the Docusign SDK in Node.js, but no matter what API I try run, I get the error 404, with this Html code as a response.
I am including the code here:
const {email, name, templateId} = body;
const token = await DocuSign.token();
const dsAPIClient = new ApiClient();
dsAPIClient.setBasePath(DOCUSIGN_BASE_PATH as string);
dsAPIClient.addDefaultHeader('Authorization', `Bearer ${token}`);
const envelopeApi = new docusign.EnvelopesApi(dsAPIClient);
const envelope = this.makeEnvelope({email, name, templateId});
console.log(envelope);
// const result = await envelopeApi.createEnvelope(ACCOUNT_ID as string, {
// envelopeDefinition: {},
// });
const result = await axios.post(
`${DOCUSIGN_BASE_PATH}/v2.1/accounts/${ACCOUNT_ID}/envelopes`,
envelope,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
// console.log('This is the result', result);
return result.data;
This is the error that I am getting, I get the same error on SDK and Axios both.
Symbol(kOutHeaders)]: [Object: null prototype]
' <h2>404 - File or directory not found.</h2>\r\n' +
' <h3>The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.</h3>\r\n' +
' </fieldset></div>\r\n' +
'</div>\r\n' +
'</body>\r\n' +
'</html>\r\n'
},
isAxiosError: true,
toJSON: [Function: toJSON]
Your code has the SDK code commented, so I'm not sure why you did this.
Here is code that works, you can find it in GitHub.
An even easier option is to use the quickstart.
let dsApiClient = new docusign.ApiClient();
dsApiClient.setBasePath(args.basePath);
dsApiClient.addDefaultHeader("Authorization", "Bearer " + args.accessToken);
let envelopesApi = new docusign.EnvelopesApi(dsApiClient),
results = null;
let envelope = makeEnvelope(args.envelopeArgs);
function makeEnvelope(args) {
let docPdfBytes;
docPdfBytes = fs.readFileSync(args.docFile);
let env = new docusign.EnvelopeDefinition();
env.emailSubject = "Please sign this document";
let doc1 = new docusign.Document(),
doc1b64 = Buffer.from(docPdfBytes).toString("base64");
doc1.documentBase64 = doc1b64;
doc1.name = "Lorem Ipsum"; // can be different from actual file name
doc1.fileExtension = "pdf";
doc1.documentId = "3";
env.documents = [doc1];
email
let signer1 = docusign.Signer.constructFromObject({
email: args.signerEmail,
name: args.signerName,
clientUserId: args.signerClientId,
recipientId: 1,
});
let signHere1 = docusign.SignHere.constructFromObject({
anchorString: "/sn1/",
anchorYOffset: "10",
anchorUnits: "pixels",
anchorXOffset: "20",
});
let signer1Tabs = docusign.Tabs.constructFromObject({
signHereTabs: [signHere1],
});
signer1.tabs = signer1Tabs;
let recipients = docusign.Recipients.constructFromObject({
signers: [signer1],
});
env.recipients = recipients;
env.status = "sent";
return env;
}

Nodejs and Mailgun: Send generated attachment *not* from file system

I generate attachments in a node app and would like to send them using Mailgun. I have no access to the file system (Netlify functions).
Is there an easy way to accomplish that?
The hole picture
It's a Jamstack (Gatsby/React, Netlify, Mailgun) web app. Customers configure and request offers. Browser generates and posts offers and images to a Netlify function (fetch api). The function send the offer mail with the PDF-offer and images attached.
Code I tested (edit: 02/25)
const path = require('path');
const fs = require('fs');
const mailgun = require('mailgun-js')
const FormData = require('form-data');
const { Readable } = require('stream');
const API_KEY = 'SECRET';
const DOMAIN = 'brasilius.de';
const mg = mailgun({apiKey: API_KEY, domain: DOMAIN, host: "api.eu.mailgun.net"});
const stream = fs.createReadStream('test.txt'); // This works
/* {
id: '<20210225115125.1.BF14CC322F8E0DAC#brasilius.de>',
message: 'Queued. Thank you.'
} */
/*
const stream = Readable.from(["Send this text as attachment."]) // Won't work
{ message: "'from' parameter is missing" }
*/
const data = {
from: 'Excited User <me#brasilius.de>',
to: 'test#user.de',
subject: 'Hello',
text: 'Testing some Mailgun awesomeness!',
attachment: stream
};
mg.messages().send(data, (error, body) => {
console.log(body);
});
The simplest solution I found here
// https://thecodebarbarian.com/sending-emails-using-the-mailgun-api.html
const mailgun = require('mailgun-js')
const mg = mailgun({apiKey: process.env.API_KEY, domain: process.env.DOMAIN, host: "api.eu.mailgun.net"});
const filename = 'test.txt';
const text = "Example test content."
const attch = new mg.Attachment({data: Buffer.from(text), filename: filename})
const data = {
from: process.env.FROM,
to: process.env.TO,
subject: 'Hello',
text: 'Testing Mailgun attachments.',
attachment: attch
};
mg.messages().send(data, (error, body) => {
console.log(body);
});
As #Joe recommended, a solution with Nodemailers Mailcomposer:
// See: https://documentation.mailgun.com/en/latest/api-sending.html#examples
// See: http://nodemailer.com/extras/mailcomposer/#attachments
const mailgun = require('mailgun-js')
const MailComposer = require('nodemailer/lib/mail-composer');
const mg = mailgun({apiKey: process.env.API_KEY, domain: process.env.DOMAIN, host: "api.eu.mailgun.net"});
const mailOptions = {
from: process.env.FROM,
subject: 'Hello',
text: 'Testing Mailgun attachments.',
attachments: [
{ // utf-8 string as an attachment
filename: 'text.txt',
content: 'For testing just a text file. This could be a ReadStream, Buffer or other.'
}
]
};
const mail = new MailComposer(mailOptions);
mail.compile().build(function(mailBuildError, message) {
var dataToSend = {
to: process.env.TO,
message: message.toString('ascii')
};
mg.messages().sendMime(dataToSend, function(sendError, body) {
if (sendError) {
console.log(sendError);
return;
}
});
});

How to email multiple recipients in sendgrid v3 node.js

Can someone help me send an email to multiple recipients in sendgrid v3 + node.js? I've noticed that when I enter several email addresses in the to field, only the first email address receives the email. The email addresses after the first one do not receive the email:
send: function(email, callback) {
var from_email = new helper.Email(email.from);
var to_email = new helper.Email('emailUser1#gmail.com,emailUser2#gmail.com,emailUser3#gmail.com');
var subject = email.subject;
var content = email.content
var mail = new helper.Mail(from_email, subject, to_email, content);
var sg = require('sendgrid')(process.env.SENDGRID_API_KEY);
var request = sg.emptyRequest({
method: 'POST',
path: '/v3/mail/send',
body: mail.toJSON(),
});
sg.API(request, function(err, res) {
console.log(res);
if(err) {
console.log('---error sending email:---');
console.log(err);
console.log(err.response.body);
callback(500);
} else {
callback(200);
}
});
}
In the example above, only emailUser1#gmail.com receives the email; emailUser2#gmail.com and emailUser3#gmail.com do not receive the email.
Can someone help?
Thanks in advance!
The node js mail helper allows you to send to multiple recipients by specifying the to property as an array. Then depending on whether you want the recipients to be able to see each other's addresses, you send the message in slightly different ways:
To allow seeing, use sgMail.send(msg):
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: ['recipient1#example.org', 'recipient2#example.org'],
from: 'sender#example.org',
subject: 'Hello world',
text: 'Hello plain world!',
html: '<p>Hello HTML world!</p>',
};
sgMail.send(msg);
To prevent seeing, use sgMail.sendMultiple(msg) or sgMail.send(msg, true)
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: ['recipient1#example.org', 'recipient2#example.org'],
from: 'sender#example.org',
subject: 'Hello world',
text: 'Hello plain world!',
html: '<p>Hello HTML world!</p>',
};
sgMail.sendMultiple(msg);
https://github.com/sendgrid/sendgrid-nodejs/blob/b3b2092b7a150ffc5d68c9bb6575810a5827f69e/docs/use-cases/single-email-multiple-recipients.md
Under the covers the helper uses Personalizations which you can use for greater control:
https://sendgrid.com/docs/for-developers/sending-email/personalizations/
const sgMail = require('#sendgrid/mail');
module.exports.send = function () {
sgMail.setApiKey('XYZ');
const msg = {
to: ['abc#gmal.com', 'xyz#gmail.com'],
cc: ['test#gmail.com', 'testing#gmail.com'],
from: 'no-reply#mail.total.fr',
subject: 'Subject of mail',
html: 'html body',
text: 'text message'
};
// console.log('message in mail :: ', msg);
sgMail.send(msg).catch(console.error);
};
Are you using SendGrid's Helper Library? You're going to want to leverage Personalizations.
If you want the recipients to see each other, you should name & populate each of the recipients within a single Personalization object. If you don't want them to see each other, and want them to each receive the message distinctly, you'll want to make a new Personalization object for each distinct recipient group.
Sendgrid API V3
Hope this helps.
https://www.npmjs.com/package/sendgrid-v3-node
Example:
https://runkit.com/embed/ne9asbfj59fr
var sendgrid = require("sendgrid-v3-node")
const mailOptions = {
sendgrid_key: 'SENDGRID_KEY',
from_email: 'FROM_EMAIL',
from_name: 'FROM_NAME',
to: ['TO_EMAIL1', 'TO_EMAIL2']
};
mailOptions.subject = 'SUBJECT';
mailOptions.content = 'CONTENT';
sendgrid.send_via_sendgrid(mailOptions).then(response => {
console.log(response);
});
{
"from": "sender#yourdomain.com",
"template_id": "YOUR TEMPLATE ID",
"personalizations": [
{
"to": [
{
"email": "john#example.com"
}
],
"send_at": 1600188812
},
{
"to": [
{
"email": "jane#example.com"
}
],
"send_at": 1600275471
}
]
}

How to add ics file events in calendar using nodejs

There is a scenario where I need to send event meeting invites to end users. I am able to generate the ICS file and send it as attachment. But the ICS files are not readable or added in User Calander.
Code to generate and send email is as below:
var transport = require('nodemailer-smtp-transport'),
transporter = nodemailer.createTransport(transport(config.mailer)),
sendMail = function(mailOptions) {
transporter.sendMail(mailOptions, function(err, response) {
if (err) return err;
return response;
});
},
eventEmailToUser = function(user, events, createdBy, mailOptions) {
var ical = require('ical-generator');
var cal = ical();
var username = user.username ? user.username : ' ';
var eventName = events.title ? events.title : ' ';
var eventDate = moment.tz(events.date, 'Asia/Kolkata');
eventDate = eventDate.format('YYYY-MM-DD h:mm a');
cal.addEvent({
start: new Date(),
end: new Date(new Date().getTime() + 3600000),
summary: events.title,
uid: events._id, // Some unique identifier
sequence: 0,
description: events.description,
location: events.location,
organizer: {
name: createdBy.username,
email: createdBy.email
},
method: 'request'
});
var path = '/files/' + events._id + '.ics';
cal.save(path, function(err, file) {
if (err) return err;
});
mailOptions.alternatives = [{
contentType: "text/calendar",
contents: new Buffer(cal).toString()
}];
mailOptions.attachments = [{
filename: events.title + '.ics',
filePath: path
}];
mailOptions.html = [
'<div>',
'<div>Hi <b>' + username + '</b><br/<br/>',
' You have just confirmed to attend <b>' + eventName + '</b> on <b>' + eventDate + '</b>',
' <br/><br/>',
'Thanks',
' <br/>',
'</div>',
'<br/>',
'</div>'
].join('\n\n');
mailOptions.subject = 'Invitation for' + eventName;
return mailOptions;
};
exports.sendInvite = function(req, res) {
var userMailOptions = {
to: 'abc#gmail.com',
from: 'xyz#gmail.com',
};
userMailOptions = eventEmailToUser(user, events, eventCreator, userMailOptions);
var userEmailresult = sendMail(userMailOptions);
};
The first issue that strikes me is that you are missing an ATTENDEE property which is required in any iMIP REQUEST.
There might be other issues but we would need to see your full MIME message instead of your code to really spot those. As a starter, you might want to doublecheck Multipart email with text and calendar: Outlook doesn't recognize ics

AWS Lambda not firing Email via nodemailer, but does in the local development environment

I'm working on a aws-lambda which is supposed to shoot mail when an event is triggered. I using nodejs for this and below is the code:
"use strict";
exports.sendEmail = function(event, context, callback) {
var config = require('./config');
var fs = require('fs');
var _ = require('lodash');
if (_validSchema(event.payload)) {
var templatePath = config.schemaMapping[event.payload.emailDetails.emailType]["templatePath"]
var emailHTML = _getHTML(templatePath, event.payload.params)
if (emailHTML && templatePath) {
_sendSESEmail(_emailParams(event.payload.emailDetails), emailHTML)
context.succeed(JSON.stringify(_setResponse(200, [{
code: "11",
source: "Email template or Email params in payload",
message: "Please provide correct Email template and correct email params",
detail: "Template path is provided via config and Params via Payload"
}])));
} else
context.fail(JSON.stringify(_setResponse(400, [{
code: "01",
source: "Email template or Email params in payload",
message: "Please provide correct Email template and correct email params",
detail: "Template path is provided via config and Params via Payload"
}])));
} else {
context.fail(JSON.stringify(_setResponse(400, [{
code: "02",
source: "Payload schema",
message: "Please provide correct schema to validate and a payload validating it",
detail: "Payload is provided "
}])));
}
function _validSchema(payload) {
var schemaPath = config.schemaMapping[payload.emailDetails.emailType]["schemaPath"];
var payloadVerification = _verifyPayload(payload, schemaPath);
console.log(payloadVerification.valid);
return payloadVerification.valid;
}
function _emailParams(emailDetails) {
var details = {};
details.to = _.join(emailDetails.to, ',');
details.from = emailDetails.from;
details.cc = _.join(emailDetails.cc, ',');
details.bcc = _.join(emailDetails.bcc, ',');
details.attachments = emailDetails.attachments;
details.subject = emailDetails.subject;
return details;
}
function _verifyPayload(payload, schemaPath) {
var schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
var Validator = require('jsonschema').Validator;
var verifier = new Validator();
console.log(verifier.validate(payload, schema))
return verifier.validate(payload, schema);
}
function _setResponse(status_code, error_list) {
return {
status: status_code,
errors: error_list
};
}
function _sendSESEmail(email, emailHTML) {
var nodemailer = require('nodemailer');
var sesTransport = require('nodemailer-ses-transport');
var transporter = nodemailer.createTransport(sesTransport({
accessKeyId: config.SES.accessKeyId,
secretAccessKey: config.SES.secretAccessKey
}));
transporter.sendMail({
from: email.from,
to: email.to,
cc: email.cc,
bcc: email.bcc,
attachments: email.attachments,
subject: email.subject,
html: emailHTML
});
}
function _getHTML(templateFile, params) {
var ejs = require('ejs');
console.log({ params: params })
var baseHTML = fs.readFileSync(templateFile, 'ascii');
return ejs.render(baseHTML, { params: params });
}
}
Above code works fine when tested in the dev environment with the below code, but does not fire a mail when tested on aws-lamda.
"use strict";
var exports = require('./exports');
var bankDetailsSchemaSample = {
"payload": {
"emailDetails": {
"from": 'some#something.com',
"to": ['kunal#something.com'],
"subject": 'My Amazon SES Simple Email',
"html": '',
"cc": ['nimesh.verma#something.com'],
"bcc": ['lokesh.gour#something.com'],
"emailType": 'bankDetails',
"attachments": [{
"filename": 'test.md',
"path": 'https://raw.github.com/nodemailer/nodemailer/master/LICENSE'
}]
},
"params": {
"orderId": 1234567,
"firstName": "Nimesh",
}
}
}
var context = {
fail: function(x) { console.log(" Fail " + x) },
succeed: function(x) { console.log(" Success " + x) }
}
exports.sendEmail(bankDetailsSchemaSample, context, {})
I can't find out, why this is happening, I also tried it using nodemailer-smtp-transport instead of nodemailer-ses-transport but the same results were obtained. When nothing helped I tried using aws-sdk instead of nodemailer and nodemailer-ses-transport and the mail is fired in both dev environment as well via aws lamda testing.
// load aws sdk
exports.sendEmail = function(event, context, callback) {
var aws = require('aws-sdk');
// load aws config
aws.config.loadFromPath('config.json');
// load AWS SES
var ses = new aws.SES({ apiVersion: '2010-12-01' });
// send to list
var to = ['nimesh.verma#something.com']
// this must relate to a verified SES account
var from = 'some#something.com'
// this sends the email
// #todo - add HTML version
ses.sendEmail({
Source: from,
Destination: { ToAddresses: to },
Message: {
Subject: {
Data: 'A Message To You Rudy'
},
Body: {
Text: {
Data: 'Stop your messing around',
}
}
}
}, function(err, data) {
if (err) throw err
console.log('Email sent:');
});
}
Why is this happening?
The problem was that the context.succeed method should be placed in the callback of _sendSESEmail method.
The complete working code is present at: https://github.com/nimeshkverma/aws-lambda-node-mailer

Resources