Sendgrid API: How to send bulk emails with individual template data? - javascript

Requested behaviour:
I would like to send out bulk emails using Sendgrid and Firestore Cloud functions together. The email should display some individual user data depending on the recipient (e.g. user id and user name).
Current State
I created a working cloud function. It gets the user data from firestore and sends out emails by using the sendgrid API to the given email addresses.
Issue
However, it sends the ids of all users to every email address, instead of only sending the id belonging to a certain subscriber. Example:
There are 3 subscribers with 3 ids in my firestore collection:
"abc", "pqr", "xyz"
The function should deliver three emails including the id belonging to the email adress. Instead, the function sends "abcpqrxyz" to every address right now.
My cloud function:
export const sendAllEmails = functions.https.onCall(async (event) => {
const subscriberSnapshots = await admin.firestore().collection('subscribers').get();
const emails = subscriberSnapshots.docs.map(snap => snap.data().email);
const ids = subscriberSnapshots.docs.map(snap => snap.id);
const individualMail = {
to: emails,
from: senderEmail,
templateId: TEMPLATE_ID,
dynamic_template_data: {
subject: event.subject,
text: event.text,
id: ids // sends all ids to everyone instead of a single id in every mail
}
await sendGridMail.send(individualMail);
return {success: true};
});
Do I need to loop over emails and IDs or does the SendGrid API have a smarter implementation for this behaviour?

You have to read over each one of these elements to get the value and construct the object.
Maybe the parameters are not being included correctly in the body of the request. Please note that the personalizations object contains the data provided for each one of these mails.
https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/#send-a-transactional-email
var data = JSON.stringify({
"personalizations": [
{
"to": [
{
"email": "john.doe#example.com",
"name": "John Doe"
}
],
"dynamic_template_data": {
"verb": "",
"adjective": "",
"noun": "",
"currentDayofWeek": ""
},
"subject": "Hello, World!"
}
],
"from": {
"email": "noreply#johndoe.com",
"name": "John Doe"
},
"reply_to": {
"email": "noreply#johndoe.com",
"name": "John Doe"
},
"template_id": "<<YOUR_TEMPLATE_ID>>"
});
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
console.log(this.responseText);
}
});
xhr.open("POST", "https://api.sendgrid.com/v3/mail/send");
xhr.setRequestHeader("authorization", "Bearer <<YOUR_API_KEY_HERE>>");
xhr.setRequestHeader("content-type", "application/json");
xhr.send(data);

You need to make multiple "personalizations" objects within the data. I'm not sure what the limitations are with the maximum amount of emails in one request. You might need to research that and check. However I tested with the following json data, and it worked for me:
let data = JSON.stringify({
"from": {
"email": senderEmail
},
"personalizations": [
{ //first individual starts here
"to": [
{
"email": "john.doe.1#example.com"
}
],
"dynamic_template_data": {
"subject": "The subject",
"text": "Event Text goes here",
"id": "abc" // the individual id you need goes here
}
},
{ //next individual starts here
"to": [
{
"email": "john.doe.2#example.com"
}
],
"dynamic_template_data": {
"subject": "The subject",
"text": "Event Text goes here",
"id": "pqr" // the individual id you need goes here
}
} //end - but one can add more indivuals
],
"template_id": "[your_template_id]" //add your template id here
});

Related

How to validate dynamic values in postman

I want to validate some mandatory fields in my JSON response body. Until now I am using a static way of testing by using hardcoded values something like this
json_response = JSON.parse(responseBody);
x=json_response
pm.expect(x).to.equal("abc");
But I want to rerun my test scripts so I don't want to change my tests again and again to validate the values. Could anyone please suggest how can I validate my response body.
{
"Name": "John",
"Contact number": 9826363660,
"Address": "xyz"
}
As every time I will get new values in these keys "Names" "Contact number" "Address"
pm.response.json().hasOwnProperty("Name")
You can use hasOwnProperty to check whether the field exists
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
Do schema validation
var schema = {
type: "object",
properties: {
"NAME": {
"type":"string"
},
"ADDRESS": {
"type":"string"
},
"Contact Number": {
"type":"number"
}
}
};
pm.response.to.have.jsonschema(schema)
https://postman-quick-reference-guide.readthedocs.io/en/latest/schema-validation.html

How do I loop through a json array, and then display the results in an discord js embed?

Thanks for taking a peek at my question. This is my first question, so hopefully I do all the right stuff.
I am using DiscordJS/NodeJS
I have a large(ish) json file that has a list of maps/links to download the map, and an extra field.
[
{
"name": "2 Evil Eyes",
"link": "http://",
"extra": ""
},
{
"name": "25 To Life",
"link": "http://",
"extra": ""
},
{
"name": "Back To School",
"link": "http://",
"extra": ""
},
I created this file myself from a list that was given to me.
The goal of this file was to be able to display the data (maps, link, extra) in a discord embed. I know that having 70+ .addField(s) is not a great idea, but it is what was requested.
So I thought that I would just have a little loop through and display the data that way, but my issue is that it keeps posting an embed for each result it gets. Here is my code below:
fs.readFile('./maps.json', 'utf-8', function(err, data){
json = JSON.parse(data);
for(let i = 0; i < data.length; i++){
let name = data[i].name;
let link = data[i].link;
let extra = data[i].extra;
}
const mapRot = new Discord.MessageEmbed()
.setTitle("Map Details")
.setAuthor(client.user.username)
.setDescription("These are your maps for the night.")
.addField("Maps", name)
.addField("Link", link)
.addField("Extra", extra);
message.channel.send(mapRot);
Any help would be appreciated!
Welcome to StackOverflow! I'm not too familiar with the Discord API, however, it seems as though you can do something like this, where you pass to addFields a list of arguments.
So I would think you could first map through the array to transform the data, and then pass it in as it should be specified to addFields. Something along the lines of:
fs.readFile('./maps.json', 'utf-8')
.then(JSON.parse)
.then(data => data.map((entry) => {
// here, 'entry' stores an object of the form:
// {
// "name": "2 Evil Eyes",
// "link": "http://",
// "extra": ""
// }
// and per the Discord documentation, needs to return
// an object of the form:
// {
// "name": 'Inline field title',
// "value": 'Some value here'
// }
// for example:
return { name: entry.name, value: entry.value }
}))
.then((parsedEntries) => {
new Discord.MessageEmbed()
.setTitle("Map Details")
.setAuthor(client.user.username)
.setDescription("These are your maps for the night.")
.addFields(...parsedEntries)
// that last .addFields call is expecting parsedEntries to be of the form
// [{name: "field name", value: "field value"}, {name: "field2", value: "field2value"}]
})
Edit: essentially, you have a list of objects with properties that look like this:
{
"name": "2 Evil Eyes",
"link": "http://",
"extra": ""
}
But addFields takes objects with properties that look like this:
{
"name": "Field title"
"value": "Field value"
}
So you can use the JavaScript map function to map over each entry in the list, converting objects from your original format as specified in your maps.json file to the one that the Discord API requires.
Here's an article that might help introduce the essential concept of JavaScript mapping if you aren't previously familiar with it.

Unable to read objectValue data using "level" query

According to the smartsheet API Docs, I should be able to use "level" parameter in my options to get a complex object for Multi-Contact columns.
Unfortunately all I'm getting in return is value and displayValue.
Am I doing something wrong here?
var options = {
id: SHEET_ID, //Id of sheet
queryParameters = {
include: ["objectValue"],
level: 1
}
}
ss.sheets.getSheet(options)
.then(function (results) {
console.log(results.rows[args[0]].cells[6])
})
The above code returns:
{ columnId: 8746190272522116, displayValue: 'John Smith, Danny Doe' }
I've verified (using Postman) that Smartsheet API does indeed support the scenario you've described. i.e., if I submit this Get Sheet request:
https://api.smartsheet.com/2.0/sheets/5831916227192708?include=objectValue&level=1
...then the response does include the complex object for a multi-contact cell in my sheet:
{
"id": 5831916227192708,
...
"rows": [
{
"id": 5942480978372484,
...
"cells": [
{
"columnId": 3992195570132868,
"objectValue": {
"objectType": "MULTI_CONTACT",
"values": [
{
"objectType": "CONTACT",
"email": "john_doe#test.com",
"name": "John Doe"
},
{
"objectType": "CONTACT",
"email": "jane_doe#test.com",
"name": "Jane Doe"
}
]
},
"displayValue": "John Doe, Jane Doe"
},
...
]
},
...
]
}
However, it looks like the Smartsheet JavaScript SDK doesn't yet support this scenario.
It's not unusual for SDK updates to lag a bit behind the release of new API features. You might consider logging an issue in the JavaScript SDK repo to request that support for this scenario be added -- or better yet, submit a PR to that repo that adds support for this scenario. In the meantime, you'll need to implement this functionality yourself within your integration (i.e., since you can't rely on the out-of-the-box SDK functionality to provide it at this time).
You just need to remove the array notations from your options definition:
var options = {
id: SHEET_ID, //Id of sheet
queryParameters = {
include: "objectValue",
level: 1
}
}
ss.sheets.getSheet(options)
.then(function (results) {
console.log(results.rows[args[0]].cells[6])
})

Send message depending on length of array

I have a problem where I have example document in mongo atlas database:
{
"_id": {
"$oid": "5e517a946364cc48f0ccf1e7"
},
"firstName": "checkout1",
"lastName": "",
"companyName": "",
"phoneNumber": null,
"email": "exampleemail#gmail.com",
"country": "",
"adress": "",
"postcode": "",
"userId": "5daf414818d091616a0d917e",
"orderedItems": [{
"_id": "5e03b2072e0c98b9fa62388c",
"id": 3,
"title": "Blue shoes",
"img1": "product4/1.jpg",
"img2": "product4/2.jpg",
"cost": 70,
"color": "blue",
"quantity": 5
}],
"createdAt": {
"$date": "2020-02-22T19:01:40.228Z"
},
"updatedAt": {
"$date": "2020-02-22T19:01:40.228Z"
},
"__v": 0
}
I want to send a message with confirmation about purchased items and their quantity as shown below:
...
const {
...
email,
orderedItems
} = req.body;
var user = await User.findOne({ email: email });
let newCheckout = await Checkout.create({
...
email,
...
orderedItems,
userId: user._id
});
const htmlEmail = `
<div>Title of first ordered item: ${newCheckout.orderedItems[0].title}</div>
`;
const mailOptions = {
from: process.env.MY_TEST_EMAIL_ADRESS,
to: process.env.MY_EMAIL_ADRESS,
subject: 'new message',
replyTo: process.env.MY_EMAIL_ADRESS,
text: process.env.MY_TEST_EMAIL_ADRESS,
html: htmlEmail
};
transporter.sendMail(mailOptions, (err, info) => {});
...
I need this part of code:
const htmlEmail = `
<div>Title of first ordered item: ${newCheckout.orderedItems[0].title}</div>
`;
To map it similar to like in React where I can map the orderedItems array to div elements so in the end, the outcome message would look something like this (user would get all ordered item titles, and the number of div elements would depend on length of array):
<div>Item: ${newCheckout.orderedItems[0].title}</div>
<div>Item: ${newCheckout.orderedItems[1].title}</div>
<div>Item: ${newCheckout.orderedItems[2].title}</div>
My main question would be is it possible to do without template engines such as Jade, Pug, Mustache?
Template engines are not a requirement, just iterate by orderedItems:
const htmlMail = newCheckout.orderedItems.map(i => `<div>Item: ${i.title}</div>`).join('')
Yes.
orderedItems.map(item => `<div>Item: ${item.title}</div>`).join()
When you find yourself coming back to StackOverflow to ask about the next step in building your own custom rendering engine, take a minute to look at Knockout.
It will save you time in the long run, because you are going to want to make more and more customisations, and soon you have spent more time making a hack than you would have making it with a renderer.
Knockout is easy and you send a JSON object and it gets rendered on the client, and you can make customisations to the layout easily and quickly. You'll love it.

Modify Response Object in strapi

I would like to get a modified response object. For example I dont know how to get the user object without the roles.
The default response is:
{
"id": 6,
"username": "username",
"email": "user#email.com",
"provider": "local",
"confirmed": true,
"blocked": false,
"role": {
"id": 2,
"name": "Authenticated",
"description": "Default role given to authenticated user.",
"type": "authenticated"
}
}
Now I want to get the same response without the role attribute.
{
"id": 6,
"username": "username",
"email": "user#email.com",
"provider": "local",
"confirmed": true,
"blocked": false
}
Currently you cannot do this in the Rest API unless you change the UserController provided by permissions plugin, which is not recommended.
What you can do then is to use the GraphQL plugin provided by Strapi, so you can query only the fields you need on client side.
The docs about how to use GraphQL plugin are here.
For anyone still struggling with this problem:
The latest versions of strapi do support custom queries, you can pass an array containing all the names of relations you wish to populate (only relations!).
If you don't want to populate any relationships, you can keep it empty, your controller would then look something like this:
module.exports = {
UserWithoutRoles: ctx => {
return strapi.query('user').findOne({ id: ctx.params.id }, ['']);
}
}
If you do wish to populate it, it would be like this:
module.exports = {
UserWithoutRoles: ctx => {
return strapi.query('user').findOne({ id: ctx.params.id }, ['role']);
}
}
Also see:
[https://strapi.io/documentation/3.0.0-beta.x/concepts/queries.html#api-reference][1]

Categories

Resources