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;
}
Related
So I'm building an API prototype and I have code that reads data from a Google Sheets (serving as a CMS) but the problem is when calling the route that I defined in Express.js it is not reading data from the sheet. It works for sheet 1 but not for sheet 2.
To read the data I use this package: https://www.npmjs.com/package/google-spreadsheet
Link to a copy of the Google Sheet: https://docs.google.com/spreadsheets/d/1yx0iRPPho2H1OGrvTAspkTr2b1zwzbkxeb7hRuqqNwc/edit?usp=sharing
Relevant Code:
router.get('/getallcontent', async function (req, res) {
const doc = new GoogleSpreadsheet('sheetId');
await doc.useServiceAccountAuth({
client_email: creds.client_email,
private_key: creds.private_key
});
const info = await doc.loadInfo();
const sheet = doc.sheetsByIndex[1];
const rows = await sheet.getRows()
// console.log(rows)
let contents = []
function Content(title, content, sku, author, lesson) {
this.title = title;
this.content = content;
this.sku = sku;
this.author = author;
this.lesson = lesson;
}
await rows.forEach(row => {
let content = new Content(
row._rawData[0],
row._rawData[1],
row._rawData[2],
row._rawData[3],
row._rawData[4]
)
contents.push(content)
})
res.json(Response.success(req, { content: contents }))
})
Response when calling the route:
{"request":{"result":"success","message":""},"body":{"lessons":[]}}
Expected response:
{"request":{"result":"success","message":""},"body":{"content":[{"title": "Requesting Clearance","content": "some html markup text", "sku": "requesting-clearance", "author": "John D", "lesson": "test-lesson"}]}}
Test Script does work:
async function getLessons() {
const doc = new GoogleSpreadsheet('1T8-rIN4w-T1OuYRuQ-JYI-15l9RqOXqDQ2KehWyp44E');
await doc.useServiceAccountAuth({
client_email: creds.client_email,
private_key: creds.private_key
});
const info = await doc.loadInfo();
const sheet = doc.sheetsByIndex[1];
const rows = await sheet.getRows()
rows.forEach(row => {
printContent(row)
})
}
async function printContent(rows) {
let lesson = {
title: rows._rawData[0],
content: rows._rawData[1],
sku: rows._rawData[2],
author: rows._rawData[3],
lesson: rows._rawData[4]
};
console.log(lesson)
}
Found my own answer. For some reason the contents array was being cleared after the .push and thus returned an empty array.
I am redirecting the user to docusign to sign their document. This is done with docusigns view request.
I would like the user to be required to sign in before signing.
I have been unable to find this in docusign documentation.
I did find this old stack overflow question Requiring DocuSign login to sign a document but the first link does not work.
Below is my code for the envelope creation
function makeEnvelope(args){
const env = new docusign.EnvelopeDefinition();
env.emailSubject = 'Please sign this document set';
// add 1 day reminder
const notification = new docusign.Notification();
notification.useAccountDefaults = 'false';
const reminders = new docusign.Reminders();
reminders.reminderEnabled = 'true';
reminders.reminderDelay = '1';
reminders.reminderFrequency = '1';
notification.reminders = reminders;
env.notification = notification;
const doc1 = new docusign.Document();
doc1.documentBase64 = Buffer.from(htmlPage(args.htmlArgs)).toString('base64');
doc1.name = args.documentName;
doc1.fileExtension = 'html';
doc1.documentId = '1';
env.documents = [doc1];
const signer1 = docusign.Signer.constructFromObject({
email: args.htmlArgs.submitterEmail,
name: args.htmlArgs.submitterName,
clientUserId: 1,
recipientId: 1,
routingOrder: 1 });
// Signer 2 is the supervisor. Gets sent the document after signer 1 signs
const signer2 = docusign.Signer.constructFromObject({
email: args.htmlArgs.supervisorEmail,
name: args.htmlArgs.supervisorName,
recipientId: 2,
routingOrder: 2 });
const signHere1 = docusign.SignHere.constructFromObject({
anchorString: '**signature_1**',
anchorYOffset: '10', anchorUnits: 'pixels',
anchorXOffset: '20'});
const signHere2 = docusign.SignHere.constructFromObject({
anchorString: '**signature_2**',
anchorYOffset: '10', anchorUnits: 'pixels',
anchorXOffset: '20'});
// Tabs are set per recipient / signer
signer1.tabs = docusign.Tabs.constructFromObject({
signHereTabs: [signHere1]});
signer2.tabs = docusign.Tabs.constructFromObject({
signHereTabs: [signHere2]});
env.recipients = docusign.Recipients.constructFromObject({
signers: [signer1, signer2],
});
env.status = args.status;
return env;
}
Here is the view request code
recipientView.controller = async (args) => {
const viewRequest = new docusign.RecipientViewRequest();
viewRequest.returnUrl = args.dsReturnUrl;
viewRequest.authenticationMethod = 'none';
viewRequest.email = args.signerEmail;
viewRequest.userName = args.signerName;
viewRequest.clientUserId = args.signerClientId;
viewRequest.authenticationMethod = 'Email';
return await recipientView.worker(viewRequest, args);
}
recipientView.worker = async (viewRequest, args) =>{
const dsApiClient = new docusign.ApiClient();
dsApiClient.setBasePath(args.basePath);
dsApiClient.addDefaultHeader('Authorization', 'Bearer ' + args.accessToken);
const envelopesApi = new docusign.EnvelopesApi(dsApiClient)
const results = await envelopesApi.createRecipientView(args.accountId, args.envelopeId, {recipientViewRequest: viewRequest});
return results.url;
}
I have tried different values for viewRequest.authenticationMethod
Nothing seems to work.
If you want signers to authenticate to DocuSign - they must have a membership in some DocuSign account. You cannot know or check if they do, so this method is not useful unless the envelopes are only sent within an organization to a fixed number of individuals that you know are members of the DocuSign account.
However, there are other ways to require recipient authentication and increase security over email. You can use SMS authentication, KBI or IDV for example. Read about how to use them in this blog post about recipient authentication.
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.
As part of small email CRM project, i have created a Nodejs app. While i send email through gmail send api, the messages are not grouped.
const sendObject = {}
output.data.messages.map(each => {
sendObject.threadId = each.threadId // ThreadID is unique.
each.payload.headers.map(head => {
if (head.name === 'From') {
sendObject.replyTo = head.value
} else if (head.name === 'Subject') {
if (head.value.indexOf('Re:') >= 0) {
sendObject.subject = head.value
} else {
sendObject.subject = 'Re: ' + head.value
}
} else if (head.name === 'Message-Id') {
sendObject.inReplyTo = head.value // The last thread messageId is inserted into In-Reply-To tag
sendObject.reference.push(head.value) // All the messageId are appended as part of References tag
}
})
})
const email_lines = []
email_lines.push('References:' + sendObject.reference.join(' '))
email_lines.push('In-Reply-To:' + sendObject.inReplyTo)
email_lines.push('Subject: ' + sendObject.subject)
email_lines.push('To:' + sendObject.replyTo)
email_lines.push('')
email_lines.push(req.body.body)
const email = email_lines.join('\r\n').trim()
let base64EncodedEmail = new Buffer(email).toString('base64')
base64EncodedEmail = base64EncodedEmail.replace(/\+/g, '-').replace(/\//g, '_')
emailConfig.gmail.users.messages.send({
userId: 'me',
threadId: sendObject.threadId,
resource: {
raw: base64EncodedEmail
}
}, async (err) => {
if (err) {
console.log('The Threads API returned an error: ' + err)
}
})
When i login to gmail through browser, i can see 2 different emails instead of one thread.
You should put threadId inside of the resource.
const response = await this.gmail.users.messages.send({
auth: this.oAuth2Client,
userId: this.gmailAddress,
uploadType: 'multipart',
resource: {
threadId: email.threadId,
raw: raw
}
})
For more details, you can check my Gmail-APIs-wrapper here
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