SendGrid personalization when sending emails on N to N relationship - node.js

I'm trying to implement an email price alerts system. In my situation, one email can have multiple price alerts, thus, conflicting with SendGrid when sending the alert as it receives the following error:
message: 'Each email address in the personalization block should be ' +
'unique between to, cc, and bcc. We found the first duplicate ' +
'instance of [xxxxxxx#gmail.com] in the personalizations.0.to ' +
'field.',
field: 'personalizations.0',
help: 'http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.recipient-errors'
I have not found anything that shows my use case. The best that I get is to send the same email to multiple different recipients such as: https://sendgrid.com/docs/for-developers/sending-email/personalizations/#sending-the-same-email-to-multiple-recipients
Is this something that can be achieved with SendGrid?!
My function:
function sendEmail (emailz, subject, body) {
const email = {
to: emailz,
from: 'salut#xxxxxxxxx.ro',
subject: subject,
text: body,
html: body
}
return sgMail.send(email)
.catch(error => {
console.error(error)
if (error.response) {
const {message, code, response} = error;
const {headers, body} = response;
console.error(body);
}
});
}
Actual email sending done:
try {
await sendEmail(
emailul,
'Alerta notificare pret!!!',
`Pretul produsului pe care il urmaresti este de ${pretulCurent} , sub alerta de pret setata de ${pretulDorit}. <br /> Produsul tau este: ${titlul} : ${link} `
)
} catch (e) {
await sendEmail('S-a produs o eroare', e.message)
throw e
}
Any help would be greatly appreciated. If this cannot be done using SendGrid, can you give me some pointers on other services where this situation might apply ?

Okay, if I understand you correctly, the problem here seems to be avoiding duplicates in your to email field. I suggest you check for duplicates before pushing to SendGrid (SendGrid won't do it for you) for each batch/job.
SendGrid accepts either an array of strings, or comma-delimited values, so you could try something like this:
var finalEmailul = [];
// for each email in the list, check - if it doesn't exist, push it in
if(finalEmailul.indexOf(emailul) < 0){
finalEmailul.push(emailul);
};
// then use 'finalEmailul' in your 'sendEmail' function
try {
await sendEmail(
finalEmailul, // <- use here. If you want it to be comma-delimited, use finalEmailul.join(",")
'Alerta notificare pret!!!',
`Pretul produsului pe care il urmaresti este de ${pretulCurent} , sub alerta de pret setata de ${pretulDorit}. <br /> Produsul tau este: ${titlul} : ${link} `
)
} catch (e) {
await sendEmail('S-a produs o eroare', e.message)
throw e
}

Related

Issue with "from" field using gmail API in Node JS

I have a problem setting the author of a mail in the "from" field of the RFC2822 mail (here the detail).
The problem is that the author does not appear in the received mail. The "from" field I've set looks like this:
MyName: name.surname#gmail.com
But I also tried:
MyName <name.surname#gmail.com>
None of these work, in the received mail the name is still missing looking at the original mail.
This should work because using nodemailer (with Gmail) and the same value for "from" it works. Can someone explain me what's happening? How can I solve?
EDIT: I report the code I am using as asked in one comment.
I separated the call to the API from the part that generates the mail body, so the call is:
function gmailSend(auth, mail){
const gmail = google.gmail({version: 'v1', auth});
const b64mex=mail.b64enc();
return gmail.users.messages.send(
{auth: auth,
userId: 'me',
resource:
{raw: b64mex}
}
);
}
While the parameter "mail" is generated in this way:
function genMail(candidate, clerk_email, supervisor_email){
return new Mail({from: `MyName: name.surname#gmail.com`, to: candidate.email,
subject: "Test Mail", bcc: supervisor_email,
"reply-to": clerk_email}, genMessage(candidate));
}
Mail simply compose an object that has the properties given in its first parameter, while b64enc() puts all in a string respecting the RFC2822 and encodes it base64.
EDIT2: code for Mail class.
class Mail{
constructor(headers, body) {
if(!headers.to)
throw Error("Recipient missing");
if(headers.subject){
const b64subject=new Buffer(headers.subject).toString("base64")
.replace(/\+/g, '-').
replace(/\//g, '_');
headers.subject='=?utf-8?B?'+b64subject+"?=";
}
Object.assign(this, headers);
this.body = body;
}
b64enc(){
const fields = ["Content-Type: text/html; charset=\"UTF-8\"\n",
"MIME-Version: 1.0\n",
"Content-Transfer-Encoding: 7bit\n"];
for(const prop of Object.keys(this)){
if(prop!=="body")
fields.push(`${prop}: ${this[prop]}\n`);
}
fields.push("\n");
fields.push(this.body);
const str=fields.join('');
const encodedMail = new Buffer(str).toString("base64")
.replace(/\+/g, '-').
replace(/\//g, '_');
return encodedMail;
}
}
EDIT3: I add screenshots of desired and actual behaviour:
Desired behaviour, the hidden info are the sender's email and the receiver's one:
Actual behaviour. The difference is that here there is not "MyName".
What is shown in my email client of course is based on the content of "From:" in the plain email. In the first case it is "My name <email address>", in the second it is just "email address".
In order to show a name, and not just the email address, you can format the from field the following way:
from: '"MyName" <name.surname#gmail.com>'

How to catch null value on http response

I have the following code method which is used to test for an existing user in MSGraph API
public String getGuestUserId(String AuthToken,String userEmail){
String _userId
def http = new HTTPBuilder(graph_base_user_url + "?")
http.request(GET) {
requestContentType = ContentType.JSON
//uri.query = [ $filter:"mail eq '$userEmail'"].toString()
uri.query=[$filter:"mail eq '$userEmail'"]
headers.'Authorization' = "Bearer " + AuthToken
response.success = { resp, json ->
//as the retunr json alue is an array collection we need to get the first element as we request all time one record from the filter
**_userId=json.value[0].id**
}
// user ID not found : error 404
response.'404' = { resp ->
_userId = 'Not Found'
}
}
_userId
}
This method works fine when the user is existing and will return properly from the success response the user ID property.
The issue I get is that if the user is not existing, the ID field is not existing either and the array is empty.
How can I handle efficiently that case and return a meaning full value to the caller like "User Does not exist"
I have try a catch exception in the response side but seems doe snot to work
Any idea how can I handle the test like if the array[0] is empty or does not contains any Id property, then return something back ?
Thanks for help
regards
It seems to be widely accepted practice not to catch NPE. Instead, one should check if something is null.
In your case:
You should check if json.value is not empty
You also should check if id is not null.
Please also note that handling exceptions in lambdas is always tricky.
You can change the code to:
http.request(GET) {
requestContentType = ContentType.JSON
uri.query=[$filter:"mail eq '$userEmail'"]
headers.'Authorization' = "Bearer " + AuthToken
if (json.value && json.value[0].id) {
response.success = { resp, json -> **_userId=json.value[0].id** }
} else {
// Here you can return generic error response, like the one below
// or consider more granular error reporting
response.'404' = { resp -> _userId = 'Not Found'}
}
}

undefined parameter received in Bixby function

I'm trying to process an utterance in the format "Get News from Impeachment Sage" where Impeachment Sage corresponds to an enum of publication names. Bixby is successfully understanding the utterance and trying to call my goal (GetNewsByName) but the trained Value is not arriving at the function. (This is based off the user persistence data example).
The operative portion of the function is thus:
function getNewsByName(altBrainsNames) {
// const name = "Impeachment Sage" //hard coded for testing
const url = properties.get("config", "baseUrl") + "altbrains"
console.log("i got to restdb.js and the url is ", url);
console.log("AltBrainsNames is", altBrainsNames)
const query = {
apikey: properties.get("secret", "apiKey"),
q: "{\"" + "name" + "\":\"" + name + "\"}"
// q: "{}"
}
console.log("query", query)
const options = {
format: "json",
query: query,
cacheTime: 0
}
const response = http.getUrl(url, options)
if (response) {
const content1 = response
// const altBrainsData = response[0][properties.get("config", "altbrainsName")]
// altbrainsData.$id = response[0]["_id"]
console.log('content1', content1);
console.log('identifier', content1)
return content1
} else {
// Doesn't exist
console.log('doesnae exist');
return
}
}
What is happening here where the Value is not reaching the function?
The Action model is:
action (GetNewsByName) {
description ("Get news data from remote Content db by searching on AltBrain name")
type (Calculation)
output (Content)
collect {
input (altBrainsNames) {
type (AltBrainsNames)
min (Required) max (One) //this means js must catch error when multiple names offered
}
}
}
We resolved this offline, just wanted to follow up on the public channel to any fellow Bixby developers seeing this question posted. The function that calls 'getNewsByName' needs to receive the input parameter. Once populated, the action worked successfully.

Search information by user on firebase

This is probably very simple ,so sorry...
I'm trying to create an application where i can create users and store informations by Firebase. There is an example here :
http://image.noelshack.com/fichiers/2015/11/1426182307-log.png
But now I want to check if the name already exists if someone wants to create a new user.
How would I go about grabbing individual user information without knowing their id , like simplelogin:54?
I've found this topic Get users by name property using Firebase but it is not the same thing because in my case I don't know the children after "Users"
Cheers,
Like Frank said, you must know something about the user to be able to look her/him up.
However, here is a general answer. I am assuming that by "name" you mean the property "identifiant" that you've created.
Suggestions
Start by looking over the Firebase Query documentation.
Short Answer
To check if a user exists by the identifiant property, you'd orderByChild("identifiant") and query for a specific user with the .equalTo("<identifient_here>").
For example, to check if a user with identifient="aaa",
var usersRef = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/Users");
var identifient = "aaa";
usersRef.orderByChild("identifiant").equalTo(identifient).once("value", function(snapshot) {
console.log("Loaded user by identifient:",identifient,snapshot.val());
});
If instead you want to query by the key (such as simplelogin:53), you could query by using orderByKey() instead of orderByChild()... or just simply setting the ref to the user's key like so:
var userKey = 'simplelogin:53';
var userRef = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/Users" + userKey);
userRef.once("value", function(snapshot) {
console.log("Loaded user",snapshot.val());
});
Long(er) Answer
You can handle this with a user factory (see Angular Providers documentation).
You return a promise in the factory using the $q service.
Here is the Angular API documentation for $q.
Example with UserFactory
Check out this working PLNKR example.
It's tied to one of my public Firebase instances.
I created the same simplelogin:53 user in /Users like you have.
If you search for the identifient = aaa, you'll get the right user.
The controller implementation here is for example purposes, and doesn't really do anything worth while. It's just for reference.
The Data
{
"Users" : {
"simplelogin:53" : {
"identifiant" : "aaa"
}
}
}
UserFactory
.factory('UserFactory', function($q, $firebaseObject, fbUrl){
return function(userId){
var deferred = $q.defer();
if (userId.isNotEmpty()) {
var userRef = new Firebase(fbUrl + '/Users/').orderByChild("identifiant").equalTo(userId);
userRef.once("value",
function(dataSnapshot){
if (dataSnapshot.val()) {
console.log("Loaded user",dataSnapshot.val());
deferred.resolve(dataSnapshot.val());
} else {
console.info("Couldn't find user by id",userId);
deferred.reject("No user found by identifient = '"+userId+"'");
}
},
function(error){
console.error("Error loading user",error);
deferred.reject(error);
}
);
} else {
deferred.reject("No id entered!");
}
return deferred.promise;
}
})
Controller
.controller('HomeController',function($scope, UserFactory) {
$scope.identifient = '';
var showError = function(errorMessage) {
if (errorMessage) {
showUser(false);
$scope.error = errorMessage;
} else {
delete $scope.error;
}
}
var showUser = function (userObject) {
if (userObject) {
showError(false);
$scope.user = userObject;
} else {
delete $scope.user;
}
}
$scope.loadUser = function() {
var userPromise = new UserFactory($scope.identifient);
userPromise.then(function(data){
showUser(data);
}).catch(function(error){
showError(error);
});
}
})
Template
<div ng-controller="HomeController">
<h2>Home Template</h2>
<input ng-model="identifient" placeholder="identifient"/>
<button ng-click="loadUser()">Find User</button>
<hr/>
<div ng-if="user">User: {{user}}</div>
<div ng-if="error">Error: {{error}}</div>
</div>
Hope that helps.

Node.js & Express: Need to redirect to specific pages based on POST values (using MongoDB)

I am creating a blog with Node, Express and MongoDB. I'm using Mongoose to connect to MongoDB.
I have a create new post form that creates and saves new posts in MongoDB just fine.
When creating a post you can mark the post as published or leave that option unchecked. When you save the post I want you to either:
A) Be redirected to the home page if the post was published, or
B) be redirected to the post's edit/update page if the post was not marked to be published.
Here's the code in the view that I'm trying to use to accomplish the above:
addPost: function(req, res) {
return new Post(req.body.post).save(function() {
if (req.body.published === true) {
return res.redirect("/");
} else {
return res.redirect("/office/post/" + [NEED OBJECT ID HERE] + "/edit");
}
});
}
This is the corresponding view that sends the POST data:
form.web-form(method="post", action="/post/new")
fieldset.fieldset
label.form-label(for="title") Title
input.text-input(id="title", type="text", name="post[title]", placeholder="Post title")
input.text-input(id="alias", type="hidden", name="post[alias]")
label.form-label(for="subhead") Subhead
input.text-input(id="subhead", type="text", name="post[subhead]", placeholder="Post subhead")
label.form-label(for="preview") Preview
textarea.text-area(id="preview", name="post[preview]", rows="4", placeholder="Preview")
label.form-label(for="post-body") Body
textarea.text-area(id="post-body", name="post[body]", rows="5", placeholder="Main content")
input.check-box(onclick="changeButton()", id="published", type="checkbox", name="post[published]")
label.inline-label(for="published") Publish
input.btn-submit(id="submit-post", type="submit", value="Save!")
a.btn-cancel(href="/") Cancel
Any help is greatly appreciated! Thanks!
Like this?
addPost: function(req, res) {
// strip 'post[' and ']' from submitted parameters
var params = {};
for (var k in req.body)
{
params[k.substring(5, k.length - 1)] = req.body[k];
};
var post = new Post(params);
return post.save(function() {
if (params.published === true) {
return res.redirect("/");
} else {
return res.redirect("/office/post/" + post._id + "/edit");
}
});
}

Resources