Azure App Service SNAT port exhaustion on Sendgrid connection - node.js

I am having a nodejs code trigerring sendgrid email to 1000+ users. When the number users is less (100-200)this works fine. But when there are 1000+ users it fails at a point and the rest of the mails are not triggered. When i check the app service it is shown as SNAT port exhaustion.
userList.forEach(async(Element) => {
console.log(Element.userId);
let textContentUser="";
let emailContentUser="";
textContentUser=textContent;
var userData=await db.collection('masterUserDetails').find({"userId":Element.userId}).toArray();
if(userData.length>0)
{
textContentUser=textContentUser.split("{{FirstName}}").join(userData[0]["givenName"]);
textContentUser=textContentUser.split("{{FullName}}").join(userData[0]["preferredName"]);
var leadReviewerData=await db.collection('masterUserDetails').find({"userId":userData[0]["counselorEmail"]}).toArray();
if(leadReviewerData.length==0)
{
textContentUser=textContentUser.split("{{LeadReviewerFullName}}").join(userData[0]["counselorName"]);
textContentUser=textContentUser.split("{{LeadReviewerFirstName}}").join(userData[0]["counselorName"]);
}
else
{
textContentUser=textContentUser.split("{{LeadReviewerFullName}}").join(leadReviewerData[0]["preferredName"]);
textContentUser=textContentUser.split("{{LeadReviewerFirstName}}").join(leadReviewerData[0]["givenName"]);
}
}
console.log("final Text cintent: ",textContentUser);
emailContentUser=emailContent;
emailContentUser=emailContentUser.replace("***content***",textContentUser);
//console.log("final email cintent: ",emailContentUser);
const msg = {
to: Element.userId, // Change to your recipient
bcc:"support_test#abc.com",
from: {
"email": "support#abc.com",
"name": "MY APP"
}, // Change to your verified sender
subject: emailSubject,
html: emailContentUser,
attachments: [
{
filename:"image002.gif",
content: img2,
content_id:"image002",
disposition:"inline"
},
{
filename:"image004.gif",
content: img4,
content_id:"image004",
disposition:"inline"
}],
reply_to: {
"email": "support.test#abc.com",
"name": "My APP Support"
},
send_at: sendAt
}
console.log("sending mail")
sgMail
.send(msg)
.then((response) => {
console.log("Success--------"+response[0].statusCode)
//console.log(response[0].headers)
})
.catch((error) => {
// console.log("error");
console.log("Error--------"+JSON.stringify(error))
})
});
This works in local. works when deployed in azure with lesser users also.

As others have noted, you're running out of SNAT ports to make the HTTP request to the SendGrid API.
I would recommend avoiding making an HTTP request for every email you want to send and instead use the built-in APIs to send multiple emails using one HTTP request.
To do so, add a personalizations property to your msg with an array. Add objects to the array to override the msg properties for that specific personalization, properties like subject, from, to, etc. as documented here. You can add a max of 1000 items to the personalizations array. So if you want to send more than 1000 emails, you'll need to create a separate msg for every 1000 emails, but now you only send a single HTTP request for every 1000 emails, so you shouldn't run into SNAT port errors as quickly.
Now, if you're using a single email body template but want to personalize the email, you can use Substitution Tags in your template and add a substitutions property to your personalization objects with an object holding the data you want to substitute.
Note: The total collective size of your substitutions may not exceed 10,000 bytes per personalization object.
The default substitution wrapper is "{{" and "}}" for the node.js library. Here's an example how using one msg, you can send multiple emails using personalizations and substitutions:
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const recipients = [
{
emailAddress: "recipient1#email.address",
preferredName: "Jonny",
counselorName: "Smith"
},
{
emailAddress: "recipient2#email.address",
preferredName: "Jay",
counselorName: "Smith"
}
];
const msg = {
// this will create an array of objects with `to` and `substitutions ` properties
personalizations: recipients.map(r => {
return {
to: r.emailAddress,
substitutions: {
preferredName: r.preferredName,
counselorName: r.counselorName
}
}
}),
from: 'your#sendgrid.sender',
subject: 'Ahoy!',
html: `<p>Ahoy {{preferredName}}!</p>
<p>It is a pleasure to meet you.</p>
<p>Best, {{counselorName}}</p>`
};
sgMail.send(msg).then(() => {
console.log('emails sent successfully!');
}).catch(error => {
console.log(error);
});
Pay attention to the html template containing substitution tags like {{preferredName}} and {{counselorName}}. These tag names have to match with the keys of the object in the substitutions property.
Warning: Substitution tags will not automatically HTML encode user input, so you have to HTML encode it yourself to prevent HTML injection attacks. You can learn more about HTML injection attacks here.
If you don't need to store the email body template in your code, you can use Dynamic Email Template as shown in this sample. You can create Dynamic Email Templates in the SendGrid app and use handlebar templating to add template data into your emails.
Updating the previous example with Dynamic Email Templates would look like this:
const msg = {
// this will create an array of objects with `to` and `dynamicTemplateData` properties
personalizations: recipients.map(r => {
return {
to: r.emailAddress,
dynamicTemplateData: {
preferredName: r.preferredName,
counselorName: r.counselorName
}
}
}),
from: 'your#sendgrid.sender',
templateId: 'd-[REPLACE_WITH_YOUR_TEMPLATE_ID]'
};
sgMail.send(msg).then(() => {
console.log('emails sent successfully!');
}).catch(error => {
console.log(error);
});
The subject and substitutions properties have been removed. The templateId and dynamicTemplateData have been added.
Note: Dynamic Email Templates use handlebar templating which will HTML encode user input, protecting you from HTML injection.
Last suggestion: Twilio recommends schedule your bulk emails by using the sendAt property instead of sending them right now.
Quoting from the SendGrid docs:
This technique allows for a more efficient way to distribute large
email requests and can improve overall mail delivery time performance.
This functionality:
Improves efficiency of processing and distributing large volumes of email.
Reduces email pre-processing time.
Enables you to time email arrival to increase open rates.
Is available for free to all SendGrid customers.

Related

How to send Google Calendar invites from node.js using a sendgrid template?

I'm trying to send ics calendar invites to users from node.js server with the goal of getting email clients (e.g. Gmail/Outlook etc) to render them as actual invites and not just usual file attachments.
This is very similar to what Calend.ly does.
So, basically, I'm trying to get something like this in Gmail:
The flow I need is the following (on the client-side):
In my frontend app User 1 presses the schedule event button;
User 2 presses accept button.
The event gets automatically scheduled and appears in both users google calendars (without any OAuth 2.0 stuff or anything like that. Just 2 button presses).
At the same time the users get emails with event details and the .ics file attachment. But the invites should already be in their calendars.
How can I do that?
If I need to use Google Calendar API for this, then how should I at least approach this if I can't have any OAuth 2.0 stuff for my users?
What I'm currently doing is I'm generating .ics files and sending them using SendGrid. However, with .icss I can't achieve a result like in the image above. These .ics files are not invites, they are merely attachments.
So I was wondering how should I approach this at all? Is using Google Calendar API the right way to implement this? If yes, then how can it be done server-side-only without making users authenticate?
I know it's possible because Calendly does exactly this. Users just enter their emails into the input field, press submit and the event invites automatically appear in their Google calendars.
How could this be implemented?
Maybe I don't get something, but generating .ics files doesn't seem to do the trick, and at the same time Google Calendar API does not appear to be the solution as well because of OAuth2 authentication.
In their docs they say:
Your application must use OAuth 2.0 to authorize requests. No other authorization protocols are supported.
Here's the code I'm using to send emails with .ics attachments (there's also a template on SendGrid side, hence the dynamicTemplateData prop):
const SendGrid = require("#sendgrid/mail");
const attachment = {
filename: 'invite.ics',
name: 'invite.ics',
content: Buffer.from(data).toString('base64'),
disposition: 'attachment',
contentId: uuid(),
type: 'text/calendar; method=REQUEST',
};
SendGrid.send({
attachments: [attachment],
templateId,
from: {
email: config.emailSender,
name: config.emailName,
},
to: user.email,
dynamicTemplateData: {
...rest,
user,
},
headers: {
'List-Unsubscribe': `<mailto:unsubscribe.link`,
},
});
And here's how my .ics attachment files look like:
BEGIN:VCALENDAR
PRODID:-//Organization//Organization App//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
DTSTART:20210420T180000Z
DTEND:20210420T190000Z
DTSTAMP:20210418T201735Z
ORGANIZER;CN=Denis Yakovenko:MAILTO:test+1#gmail.com
UID:25bb4d3e-b69d-46b0-baea-489c71c48c88
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Denis Yakovenko;X-NUM-GUESTS=0:MAILTO:test+1#gmail.com
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=John Smith;X-NUM-GUESTS=0:MAILTO:test+2#gmail.com
CREATED:20210418T201735Z
DESCRIPTION:my description
LAST-MODIFIED:20210418T201735Z
LOCATION:https://virtual.location.com
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:my summary
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR
I referenced this issue on the sendgrid repository as well as converted the solution from the ruby version to javascript.
The following worked for me successfully and Google generated the event preview.
const ics = require("ics");
const sendgrid = require("#sendgrid/mail");
const event = {
start: [2018, 5, 30, 6, 30],
duration: { hours: 6, minutes: 30 },
title: "Bolder Boulder",
description: "Annual 10-kilometer run in Boulder, Colorado",
location: "Folsom Field, University of Colorado (finish line)",
url: "http://www.bolderboulder.com/",
geo: { lat: 40.0095, lon: 105.2669 },
categories: ["10k races", "Memorial Day Weekend", "Boulder CO"],
status: "CONFIRMED",
busyStatus: "BUSY",
organizer: { name: "Admin", email: "Race#BolderBOULDER.com" },
attendees: [
{
name: "Adam Gibbons",
email: "adam#example.com",
rsvp: true,
partstat: "ACCEPTED",
role: "REQ-PARTICIPANT",
},
],
};
const { value } = ics.createEvent(event);
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
sendgrid.send({
to: "email#example.com",
from: "test#example.com",
subject: "This is an example email 3",
content: [
{
type: "text/plain",
value: "Plain Content",
},
{
type: "text/html",
value: "HTML Content",
},
{
type: "text/calendar; method=REQUEST",
value: value,
},
],
attachments: [
{
content: Buffer.from(value).toString("base64"),
type: "application/ics",
name: "invite.ics",
filename: "invite.ics",
disposition: "attachment",
},
],
});

Mutable Value in Firebase Push Notification Node.js 10 correct syntax?

I am trying to add Mutable_Content to my push notifications so I can add a badge count.
However I am running into the error:
Messaging payload contains an invalid value for the "notification.mutable_content" property. Values must be strings.
Here is my code for the payload:
const payload = {
notification : {
title: owner + ' has made a post',
body: title + ' - ' + caption,
mutable_content : true
},
};
I have tried many different tutorials to figure out the right syntax but it is not working.
It always comes up with an error.
I am using node.js 10 for the engine and firebase/google cloud platform to trigger the function.
Can anyone help me with this syntax?
Thank you
I had the same question and found unrelated and outdated firebase documentation about it. But you can bypass this by adding a apns field where you can directly put data for the apple notification service. But beware to use the apple syntax with hyphen.
See also: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification
var message = {
notification: {
title: "my notification title",
body: "hello"
},
apns: {
payload: {
aps: {
'mutable-content': true
}
}
},

Express changes string on POST request

I'm attaching user info (got by authorization header) with each request (post/put/get/delete) to request headers
...
req.headers.user = {
phone: "+37066666666",
id: "bda5a3c0-2e95-4d6d-bf1f-6c1c8f1edcaa"
};
...
and I have POST request sent by POSTMAN (for example) where I'm sending this body as JSON object:
{
"title": "test",
"users": [
{
"phone": "+37066666666"
}
]
}
where later my code checks if there are owner in this users array and it should append owner if not. but I can't do that since user object that was set in headers have different phone number than phone number of the first user in the users object. Even the number is the same.
So I printed the number stored in headers and the one sent with POST request and this is what I saw:
the number stored in headers after utf=8 decode method: \x2B\x33\x37\x30\x36\x35\x31\x35\x36\x35\x39\x30\xE2\x80\xAC\xE2\x80\xAC\xE2\x80\xAC
and the number sent with POST request:
\x2B\x33\x37\x30\x36\x35\x31\x35\x36\x35\x39\x30
so how this can be fixed? apparently my method to find user doesn't work since the numbers "are not the same"
req.body.users.find(v => v.phone === req.headers.user.phone)
this method will return "undefined" even the printed v.phone and req.headers.user.phone values are the same (on console).
the problem was my macbook "paste" function. it was pasting invisible characters:
"users": [
{
"phone": "+370...<202c><202c>",
...
}

How do I prefill fields in an envelope created from a template using Docusign's REST API?

Note: I'm using the 'classic' experience because the new interface doesn't have a way for a template to set up required fields for future signers.
The workflow:
have a template with a bunch of fields
using the api:
create an envelope/document from the template, and assign a new user to sign (this document is going to be an agreement for signing up for a service)
create new role
set roleName to fake signer on template (because I can't configure fields without at least one signer on the template)
add textTabs to try to perfill some of the fields.
retrieve the recipients
create a recipient view so that I get the URL to put in an iframe
This is kind of obnoxious, because I don't care about having a first signer that isn't the user signing up for the service. I would however, like to have the the document be copied to someone after signing, but docusign doesn't appear to support this (that I've found anyway).
Here is the node.js code for the creation of the envelope (where I think my API usage is going wrong):
function createEnvelopeDefinition(templateId, userData) {
var envDef = new docusign.EnvelopeDefinition();
envDef.setEmailSubject('Signup Agreement');
envDef.setTemplateId(templateId);
var tRole = new docusign.TemplateRole();
tRole.setRoleName('RoleOne');
tRole.setName(userData.fullName);
tRole.setEmail(userData.email);
tRole.setClientUserId('2');
tRole.setTabs(new docusign.Tabs());
tRole.getTabs().setTextTabs([]);
const fieldsToPreFill = [
'field1',
'field2',
'field3',
'field4'];
fieldsToPreFill.forEach(fieldName => {
let textTab = new docusign.Text();
let value = userData[fieldName];
if (value === null || value === undefined) { value = 'not null'; }
textTab.setTabLabel(fieldName);
textTab.setValue(value);
tRole.getTabs().getTextTabs().push(textTab);
});
tRole = removeNulls(tRole);
envDef.setTemplateRoles([tRole]);
// send the envelope by setting |status| to 'sent'.
// To save as a draft set to 'created'
// sent is required for getting view URLs
envDef.setStatus('sent');
return envDef;
}
In the template editor on docusign, the Data Field Tag Properties show the label of each of the corresponding fields as field1, field2, etc.
These fields are now filled out with the provided values when I throw the new envelope in an iframe.
just for reference here is the rest of the code that creates the api connection, and gets the view URL
import ENV from 'environment/backend';
const accountId = ENV.docusign.accountId;
var Promise = require('bluebird');
var docusign = require('docusign-esign');
export function newApiClient() {
let apiClient = new docusign.ApiClient();
apiClient.setBasePath(ENV.docusign.endpoint);
// create JSON formatted auth header
let creds = JSON.stringify({
Username: ENV.docusign.email,
Password: ENV.docusign.password,
IntegratorKey: ENV.docusign.integratorKey
});
apiClient.addDefaultHeader('X-DocuSign-Authentication', creds);
// assign api client to the Configuration object
// this probably doesn't need to be set every time...
docusign.Configuration.default.setDefaultApiClient(apiClient);
return apiClient;
}
const defaultApiClient = newApiClient();
const envelopesApi = new docusign.EnvelopesApi();
const createEnvelope = Promise.promisify(envelopesApi.createEnvelope, { context: envelopesApi });
const listRecipients = Promise.promisify(envelopesApi.listRecipients, { context: envelopesApi });
const createRecipientView = Promise.promisify(envelopesApi.createRecipientView, { context: envelopesApi });
export default defaultApiClient;
// promise resolves to the view URL, envelopeId for the user.
// returns a recipientView
export function setupDocumentForEmbeddedSigning(templateId, userData) {
let envDefinition = createEnvelopeDefinition(templateId, userData);
return createEnvelope(accountId, envDefinition, null)
.then(envelopeSummary => {
const envelopeId = envelopeSummary.envelopeId;
return createViewFromEnvelope(envelopeId);
});
}
export function createViewFromEnvelope(envelopeId) {
return getRecipients(envelopeId).then(recipients => {
// the last signer is the one we added in the
// createEnvelopeDefinition step
let signers = recipients.signers;
let lastSigner = signers[signers.length - 1];
return createView(envelopeId, lastSigner)
.then(recipientView => [recipientView.url, envelopeId]);
});
}
function getRecipients(envelopeId) {
return listRecipients(accountId, envelopeId);
}
function createView(envelopeId, signerData) {
var viewRequest = new docusign.RecipientViewRequest();
viewRequest.setReturnUrl(ENV.host);
viewRequest.setAuthenticationMethod('email');
// recipient information must match embedded recipient info
// from the createEnvelopeDefinition method
viewRequest.setEmail(signerData.email);
viewRequest.setUserName(signerData.name);
viewRequest.setRecipientId('2');
viewRequest.setClientUserId('2');
return createRecipientView(accountId, envelopeId, viewRequest);
}
// bug with the api wrapper
// https://github.com/docusign/docusign-node-client/issues/47
const removeNulls = function(obj) {
var isArray = obj instanceof Array;
for (var k in obj) {
if (obj[k] === null) isArray ? obj.splice(k, 1) : delete obj[k];
else if (typeof obj[k] == 'object') removeNulls(obj[k]);
if (isArray && obj.length == k) removeNulls(obj);
}
return obj;
};
So, I may not fully understand where you're stuck, but I'll take a crack at this anyway...
Let's say I create a Template using the DocuSign UI and define two Recipient roles:
Signer1 (which will be the person who is signing up for your service) -- Action = "Sign"
CarbonCopy1 (which will be the person who gets a copy of the completed/signed documents once Signer1 signs) -- Action = "Receive a Copy"
(Note: these roles can be named whatever you want to name them -- I named them "Signer1" and "CarbonCopy1" so it'd be clear who each role represents.)
Assuming the above scenario, your Template's Recipient Roles (in the DocuSign UI) will look like this:
Next, let's assume that you define some fields (tabs) in the Template's document(s) (i.e., using the DocuSign UI) that the Signer1 recipient will need to populate when they sign the document(s). For this example, let's assume that the label (name) of one of those Text tabs is field1. Notice that the field is assigned to the Signer1 recipient:
Now, if I want to create an Envelope via the API that uses this Template, and pre-fill fields for one or more of the recipients, the key to doing that is using the "Composite Templates" structure in the API request. (See the Composite Templates section of this page for details.) In the example described above, your compositeTemplates object in the API request would contain a single serverTemplate object (which specifies the templateId and sequence=1), and a single inlineTemplate object (which specifies sequence=2 and the recipient info, including values for any tabs (fields) that you want to pre-fill).
In the example described above, the JSON API request to create the Envelope would look like this (assuming we're just pre-filling a single field for Signer1 -- obviously you could pre-fill additional fields by simply including them in the tabs object of the request along with field1):
POST https://{{env}}.docusign.net/restapi//v2/accounts/{{accountId}}/envelopes
{
"emailSubject": "Test Pre-fill Tabs",
"emailBlurb": "This is a test.",
"compositeTemplates": [{
"serverTemplates": [{
"sequence": "1",
"templateId": "CD0E6D53-3447-4A9E-BBAF-0EB2C78E8310"
}],
"inlineTemplates":[{
"sequence": "2",
"recipients": {
"signers": [
{
"roleName": "Signer1",
"recipientId": "1",
"name": "John Doe",
"email": "johndoe#test.com",
"clientUserId": "1234",
"tabs": {
"textTabs": [
{
"tabLabel": "field1",
"value": "TEST-123"
}
]
}
},
{
"roleName": "CarbonCopy1",
"recipientId": "2",
"name": "Jane Doe",
"email": "janedoe#test.com"
}
]
}
}]
}],
"status": "sent"
}
Once I create the Envelope using the above request, I execute a "POST Recipient View" request to get the signing URL for the first recipient (https://{{env}}.docusign.net/restapi//v2/accounts/{{accountId}}/envelopes/{{envelopeId/views/recipient).
Then, when I subsequently use the URL that's returned in that response to launch the signing session for Signer1 (John Doe), I see that the field1 tab is indeed pre-filled with the value that I specified in the "Create Envelope" API request (TEST-123):
Furthermore, once John Doe (Signer1) finishes signing and submits the completed documents, Jane Doe (CarbonCopy1) will be sent a copy.
I'm not familiar with the DocuSign Node SDK, but imagine you can figure out the syntax to use composite templates as shown in the above example. Hope this helps!

NodeJS Sendgrid Issue in sending email to multiple recipients

I am having issue in sending mails to multiple recipients.
My script is
var SendGrid = require('sendgrid').SendGrid;
var sendgrid = new SendGrid('<<username>>', '<<password>>');
sendgrid.send({
to: 'nabababa#gmail.com',
from: 'sengupta.nabarun#gmail.com',
bcc: ["sengupta.nabarun#gmail.com","sengupta_nabarun#rediffmail.com"],
I have two questions here
Can I have an array of recipients in to list?
How can I get an array of recipients in bcc list?
Solutions related to above two queries will be indeed helpful
Thanks
Nabarun
You may use an array of recipients in both the to and bcc fields.
For example:
var SendGrid = require('sendgrid').SendGrid;
var sendgrid = new SendGrid('{{sendgrid username}}', '{{sendgrid password}}');
sendgrid.send({
to: ['one#example.com', 'two#example.com'],
from: 'nick#sendgrid.com',
bcc: ['three#example.com', 'four#example.com'],
subject: 'This is a demonstration of SendGrid sending email to mulitple recipients.',
html: '<img src="http://3.bp.blogspot.com/-P6jNF5dU_UI/TTgpp3K4vSI/AAAAAAAAD2I/V4JC33e6sPM/s1600/happy2.jpg" style="width: 100%" />'
});
If this isn't working for you and Node isn't spitting out any errors, check to see if the emails are being sent, by logging into SendGrid's website and looking at the Email Activity Log.
One thing I came across while testing your code sample is if you're sending the to and bcc to the same gmail address, gmail will combine it all into one email (so it appears it didn't work). Make sure when testing you're sending email to entirely different accounts.
If you need some email accounts to test with Guerrilla Mail is an excellent option for creating temporary test accounts.
This is the solution that I ended up with and thought it was more straightforward and could be helpful for folks.
Note the difference in the shape of the personalizations object.
Recipients can see each other:
const sgMail = require('#sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
// Declare the content we'll use for the email
const FROM_EMAIL = 'example#example.io' // <-- Replace with your email
const subject = 'Test Email Subject'
const body = '<p>Hello HTML world!</p>'
const recipients = ['alice#example.com', 'bob#example.com'] // <-- Add your email(s) here to test
// Create the personalizations object that will be passed to our message object
let personalizations = [{
to: [],
subject
}]
// Iterate over our recipients and add them to the personalizations object
for (let index in recipients) {
personalizations[0].to[index] = { email: recipients[index] }
}
const msg = {
personalizations,
from: FROM_EMAIL,
html: body,
}
// Log to see what our message object looks like
console.log(msg)
// Send the email, if success log it, else log the error message
sgMail.send(msg)
.then(() => console.log('Mail sent successfully'))
.catch(error => console.error(error.toString()))
Personalizations Object:
{
personalizations: [{
to: [
{email: "alice#example.com"},
{email: "bob#example.com"},
],
subject: "Test Email Subject"
}]
}
Recipients can not see each other:
// Create the personalizations object that will be passed to our message object
personalizations = []
// Iterate over our recipients and add them to the personalizations object
for (let index in recipients) {
personalizations[index] = { to: recipients[index], subject}
}
Personalizations Object:
{
personalizations: [
{
to: "alice#example.com",
subject: "Test Email Subject"
},
{
to: "bob#example.com",
subject: "Test Email Subject"
}
]
}
I created a RunKit with the full solution and where you can test it out.
For Sendgrid's v3 API, I found their "kitchen sink" example helpful. Here is a relevant bit from it:
var helper = require('sendgrid').mail
mail = new helper.Mail()
email = new helper.Email("test#example.com", "Example User")
mail.setFrom(email)
mail.setSubject("Hello World from the SendGrid Node.js Library")
personalization = new helper.Personalization()
email = new helper.Email("test1#example.com", "Example User")
personalization.addTo(email)
email = new helper.Email("test2#example.com", "Example User")
personalization.addTo(email)
// ...
mail.addPersonalization(personalization)
The new sendgrid-nodejs update has scrapped previous implementation methods and therefore the accepted answer wont help you now.
So... just an update in case anyone lands to this thread with specific search result.
to: [
{
email: 'email1#email.com',
},
{
email: 'email2#email.com',
},
],
A solution for TypeScript (written in ts version 3.4.3 and sendGrid 7.1.1) where you don't want recipients to be able to see each other.
import * as sendGrid from '#sendgrid/mail'
type UserEmail = {
to: string
subject: string
}
// Add as many recipients as you want
recipients = ['email1#global.com', 'email2#gmail.com']
const personalizations: UserEmail[] = recipients.map(admin => ({
to: admin,
subject: 'Inject Subject Here',
}))
try {
await sendGrid.send({
from, // Inject
personalizations,
html, // Inject
})
} catch (err) {
console.log(err)
}
const personalizations looks like this
[{ to: 'email1#global.com',
subject: 'Inject Subject Here' },
{ to: 'email2#global.com',
subject: 'Inject Subject Here' }]

Resources