Sengrid template substitution tags not replaced when sending email in Meteor app - email

In Meteor application that incorporates Sendgrid transaction email templates for user invitations and notifications, I can't manage to replace substitution tags. Templated email is received, but without any difference.
Email.send({
from: "hello#domain.com",
to:email,
subject: "Subject",
sub: {
"{name}":post.createdBy,
"{title}":post.title,
},
headers: {
"X-SMTPAPI": {
"filters": {
"templates": {
"settings": {
"enable": 1,
"template_id": "xxxx"
}
}
}
},
"Content-Type" : "text/html"
}
});
I'm not using API directly, but rather Meteor Email package, but don't see that possible issue:
Meteor.startup(function () {
process.env.MAIL_URL = 'smtp://username:password#smtp.sendgrid.net:587';
});
This is my shortened email template:
Hey {name},
your post {title} has a new comment.

You need to put the subs in the X-SMTPAPI header as well. The X-SMTPAPI header itself should also contain valid JSON in a string.
Try this:
var xsmtpapi = {
"filters": {
"templates": {
"settings": {
"enable": 1,
"template_id": "xxxx"
}
}
},
"sub": {
"{name}": post.createdBy,
"{title}": post.title
}
}
Email.send({
from: "hello#domain.com",
to:email,
subject: "Subject",
sub: {
"{name}":post.createdBy,
"{title}":post.title,
},
headers: {
"X-SMTPAPI": JSON.stringify(xsmtpapi),
"Content-Type" : "text/html"
}
});

What I ended up doing was using smtpapi-nodejs NPM package.
The simple example would be:
var nodemailer = require('nodemailer');
var smtpapi = require('smtpapi');
var header = new smtpapi();
header.setFilters({
"templates": {
"settings": {
"enable": 1,
"template_id": xxx-template-id-xxx
}
}
});
header.addSubstitution('-name-', post.createdBy);
header.addSubstitution(-title-', post.title);
var headers = { 'x-smtpapi': header.jsonString() };
// Use nodemailer to send the email
var settings = {
host: "smtp.sendgrid.net",
port: parseInt(587, 10),
requiresAuth: true,
auth: {
user: "sendgrid_username",
pass: "sendgrid_password"
}
};
var smtpTransport = nodemailer.createTransport(settings);
var mailOptions = {
from: "Fred Foo <foo#blurdybloop.com>",
to: "bar#blurdybloop.com",
subject: "Hello",
text: "Hello world",
html: "<b>Hello world</b>",
headers: headers
}
smtpTransport.sendMail(mailOptions, function(error, response) {
smtpTransport.close();
if (error) {
console.log(error);
} else {
console.log("Message sent: " + response.message);
}
});

Related

SendGrid: Disable Clicktrack using Node

I am using the sendgrid-nodejs library to send emails using SendGrid.
I want to disable click-tracking on a per-email basis.
I understand that you can include an attribute within dynamic templates to disable click tracking:
Click tracking can be turned off for individual links by including the clicktracking=off attribute inside the anchor of an HTML link before the href. For example, <a clicktracking=off href="http://example.com">link text</a> would not be tracked.
However, I wish to control this programmatically.
According to SendGrid documentation, it is possible to disable click-tracking by using the clicktrack filter:
{
"filters": {
"clicktrack": {
"settings": {
"enable": 0,
"enable_text": false
}
}
}
}
Looking at the Mail constructor, it appears we have the ability to set headers. The type bindings indicate it expects header values to be a string.
headers?: { [key: string]: string }
Note: I can confirm this per SendGrid's error return (if attempting to pass an object):
{
"body": {
"errors": [
{
"message": "Invalid type. Expected: string, given: object.",
"field": "headers",
"help": "http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.headers"
}
]
}
}
Regardless of what I pass, nothing seems to have any impact. The emails are being sent successfully, but click-tracking is not being disabled.
const { SENDGRID_KEY } = process.env
const mail = require('#sendgrid/mail')
mail.setApiKey(SENDGRID_KEY)
mail.send({
headers: {
// this doesn't have any impact
"X-SMTPAPI": JSON.stringify({
filters: {
clicktrack: {
settings: {
enable: 0,
enable_text: false
}
}
}
}),
// neither does this
"filters": JSON.stringify({
clicktrack: {
settings: {
enable: 0,
enable_text: false
}
}
}),
},
to: 'somebody#email.com',
from: 'nobody#email.com',
templateId: 'd-xxxxxxxxxxxxxxxxxxxxxxxx',
dynamic_template_data: {
subject: 'Hello World'
}
})
Why isn't this working?
I've found my answer. There is a trackingSettings property available:
const { SENDGRID_KEY } = process.env
const mail = require('#sendgrid/mail')
mail.setApiKey(SENDGRID_KEY)
mail.send({
trackingSettings: {
clickTracking: {
enable: false,
enableText: false
}
},
to: 'somebody#email.com',
from: 'nobody#email.com',
templateId: 'd-xxxxxxxxxxxxxxxxxxxxxxxx',
dynamic_template_data: {
subject: 'Hello World'
}
})

(intermediate value).sendEmail(...).promise is not a function

I'm using aws-sdk in my service to send emails. I'm getting below exception to a code which was working fine before.
const aws = require('aws-sdk');
var params = {
Destination: {
ToAddresses: [
'checkMail#gmail.com'
]
},
Message: {
Body: {
Html: {
Charset: "UTF-8",
Data: "HTML_FORMAT_BODY"
},
Text: {
Charset: "UTF-8",
Data: "this is sample"
}
},
Subject: {
Charset: 'UTF-8',
Data: 'Test email'
}
},
Source: 'AWS Services<awsEmails#awsService.com>'
ReplyToAddresses: [
'AwsServices<noreply#awsServices.com>'
],
};
// Create the promise and SES service object
var emailPromise = new aws.SES({apiVersion: '2010-12-01'}).sendEmail(params).promise();
// Handle promise's fulfilled/rejected states
emailPromise.then(
function(data) {
//my logic on success goes here
}).catch(
function(err) {
//my logic on error goes here
});
I have tried using different API calls for email from AWS but all returns the same error.
Avoid require ALL aws-sdk if it's just to use a single service. For using SES, you can yarn add #aws-sdk/client-ses and then use it const { SESClient, SendEmailCommand } = require("#aws-sdk/client-ses");
I show you here a full exemple of sending email with SES in a nodeJS lambda function:
const {
SESClient,
SendEmailCommand,
} = require("#aws-sdk/client-ses");
const REGION = "eu-west-3"; // Use you AWS region
const ses = new SESClient({ region: REGION });
exports.handler = async function (event) {
const path = event.path;
if (path === "/send-email") {
const peopleAmount = 12;
const params = {
Source: "John Wick <john.wick#killer.com>",
Destination: {
ToAddresses: ["adresse1#test.com"],
},
Message: {
Body: {
Html: {
Data: `<span>This email is about <b>${peopleAmount}</b> people.</span>`,
},
},
Subject: {
Data: "Email Title",
},
},
};
try {
const data = await ses.send(new SendEmailCommand(params));
return {
statusCode: 200,
body: peopleAmount,
};
} catch (e) {
console.error(e, e.stack);
return {
statusCode: 400,
body: "Sending failed",
};
}
}
}
I hope it helps, here is documentation to use SES email template.

Loopback 4 - POST request dtasource template

I am having issue to declare POST operation in Loopback 4 datasource file.
My template is as follows:
{
"template": {
"method": "POST",
"url": "https://reqres.in/api/login"
},
"functions": {
"login": []
}
}
My service interface
login(email: string, password: string): Promise<any>;
My Controller
#post('/loginTest')
async testingLogin(
#requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(TestModel, {
title: 'Post',
}),
},
},
})
testModel: TestModel, )
: Promise<any> {
// TEST MODEL CONTAIN JSON OBJECT {email: "" , password: ""}
console.log("Test Model Representation: ", testModel)
try {
var response = await this.loginService.login(testModel.email, testModel.password);
} catch (error) {
console.log("error", error)
}
console.log("Fake POST response", response)
return response;
};
I am using this fake API : https://reqres.in/api/login
I am getting following error:
Test Model Representation: { email: 'string', password: 'string' }
error Error: {"error":"Missing email or username"}
at callback (D:\loginApp\node_modules\loopback-connector-rest\lib\rest-builder.js:541:21)
at D:\loginApp\node_modules\loopback-datasource-juggler\lib\observer.js:269:22
at doNotify (D:\loginApp\node_modules\loopback-datasource-juggler\lib\observer.js:157:49)
at RestConnector.ObserverMixin._notifyBaseObservers (D:\loginApp\node_modules\loopback-datasource-juggler\lib\observer.js:180:5) {
statusCode: 400,
message: '{"error":"Missing email or username"}'
}
Fake POST response undefined
It look like my email and password is not passed ? Thanks for any help.
The login function you defined in the datasource file should match with the service interface. That means it would be something like:
"functions": {
"login": ["email", "password"]
}

Unable to update Data

Am trying to update the json data through an api call.
I was able to GET the data without any issues, as am not passing any Options in the request.
For UPDATE,
//saga.js
export function* BlurideaTitler(opt) {
const id = opt.id; // 4
const updatedTitle = opt.newTitle; // "title changed"
let options = {
crossDomain: true,
method: 'PUT',
json: true,
headers: {'Content-Type': 'application/json'},
body: {
title: updatedTitle
}
};
const requestURL = `http://localhost:3000/ideas/${id}`;
try {
yield call(request, requestURL, options);
} catch (err) {
console.log(err);
}
}
// request.js
export default function request(url, options) {
return fetch(url, options)
.then(checkStatus)
.then(parseJSON);
}
//db.json
JSON am trying to update.,
{
"ideas": [
{
"id": 4,
"title": "My fourth Idea",
"body": "Description of my fourth idea",
"created_date": "14-Apr-2019"
}
]
}
This is supposed to update the value of title. But it throws error'Bad request' . Can someone please let me know what am missing here.

PayPal express checkout: Can't process payment with sandbox test accounts

I tried to integrate PayPal Express Checkout to my webapp (server-side REST) and I managed to:
get create-payment working
However, when I tried to login with a sandbox test buyer account, it failed to process the payment.
There's a 400 Bad request error for the POST request:
POST https://www.sandbox.paypal.com/webapps/hermes/api/batch/setbuyer 400 (Bad Request)
with the following response:
{
"ack": "success",
"data": {
"buyerEligibility": {
"ack": "success",
"data": {
"eligible": true,
"clearedRT": false
},
"meta": {
"calc": "8f7cc8e266c07",
"rlog": "PVs6nBwuIQ9X2gSdIEqzR%2BxkmohYC3WlOiW4HauQ%2FY%2Fh%2BkWFfmr2pOeyxVs3sSiqXDydWuJ%2B6QWLAsZZVtRfIA_15d276b99bf"
},
"server": "xG7Ol-1A5r4xEqHEubzBtkj6LgLo88Z7UFOPmqsoK957Q11gkENbvzjGa02RjhyhvYG_ff2SZRFgHYp0Nq5rCCIdwQAwbwL-RkZ0piofvsP6-i9NmpkouuuH47EBynDbMencyfNKhT-cIewGtGK2jKUX_q0FaWq9Gx0MaRB6QBwINBmRQB5tAuklfWE8ooIwOO7szgPXVg9pOXWI2ukxup08j93HODToZ4DnSLuqCK6XM2M49-_DDSyS6GviI3gWrBy7BLOsHky"
},
"eConsent": {
"ack": "success",
"data": {
"required": false
},
"meta": {
"calc": "8f7cc8e266c07",
"rlog": "PVs6nBwuIQ9X2gSdIEqzR%2BxkmohYC3WlOiW4HauQ%2FY%2Fh%2BkWFfmr2pOeyxVs3sSiqXDydWuJ%2B6QWLAsZZVtRfIA_15d276b99bf"
},
"server": "-bDk6FVAJFycsTL-R5q_CGdrJwDz9XbAF9KqMHpr6QIMACZ6IA5zQ_BVyqi3jy6w9pKC5SS4TBrpDB_OJC0rU1W5wz5XBgo3ze_iOG0gDEwxuzu7WtAT1Nv5_VmLhmUWIdMm7qtgfy1y11v18zXSxhATUDaRI8hNdlnArSlBtKVNGWkhCD4OTp4KvSBXQ3lLHm-wCSrJzhpEmBoNZmDQMrd4wv1YEYA0VFPG1cPHapq9t4xJMLfiZOad10irqxJP"
},
"createCheckoutSession": {
"ack": "contingency",
"contingency": "PAYER_CANNOT_PAY",
"errorData": {
"cause": "",
"step_up_context": {}
},
"meta": {
"calc": "8f7cc8e266c07",
"rlog": "PVs6nBwuIQ9X2gSdIEqzR%2BxkmohYC3WlOiW4HauQ%2FY%2Fh%2BkWFfmr2pOeyxVs3sSiqXDydWuJ%2B6QWLAsZZVtRfIA_15d276b99bf"
},
"server": "-eKOVjOLP5i0k_9Et8_N5HyfLSVBzycsA2AE8UY8RD88MnM3729QBQoHY2eD3sMhSThBqdYmvFoARIAbkHNoOT9jsHzAUCk1CtbA717xHK5gSuYujf5mvuDJQFXWlPEDBk7XFlZSyhUWy8VGKvYWwWhuTSzcjMdKIzRI_XTjfA2hQpzIvkbRQ5jLMDIIKeNm1XrF3mEMN3gkHzZIc2OBiRaVEA2Q0se_uVgEEGIbSgN2aeSOeh4WiMC08zUCvmdCLyCP0ZyE24fzDvL4ZMUurG"
}
},
"meta": {
"calc": "8f7cc8e266c07",
"rlog": "PVs6nBwuIQ9X2gSdIEqzR%2BxkmohYC3WlOiW4HauQ%2FY%2Fh%2BkWFfmr2pOeyxVs3sSiqXDydWuJ%2B6QWLAsZZVtRfIA_15d276b99bf"
},
"server": "JwJRYq2SF3kUujC16-VsiMQu8IDN_RxPNOz8wY8m8YD4P3PzhHZB73hNd_IM9PktfJcPPHx2RyVUk1PV8bC2lLtejwTFKzq-7QDM9nLmxJLw7os2tgLnGYAebFJAkmIt2fFvlncVMrAg9bAsMF9INhPqixaCEWn7ug9OcPCci_3autJi3cvmTLb_8XvTaGBpPxI0ASQnkXTSVJa2GPIptYhGVHFN5N92hFdxzwp2uYQhHeJrePmExV4NlLd0s_wa"
}
My client side implementation for PayPal Express Checkout:
class PayPalButton extends Component {
render() {
const Btn = paypal.Button.driver('react', {React, ReactDOM});
const CREATE_PAYMENT_URL = `${ROOT_URL}/paypal/create-payment/${this.props.regId}`;
const EXECUTE_PAYMENT_URL = `${ROOT_URL}/paypal/execute-payment/${this.props.regId}`;
const token = localStorage.getItem('deotoken');
let client = {
sandbox: 'TO_BE_REPLACED_WITH_SANDBOX_CLIENT_ID'
};
let payment = () => {
return paypal.request({
method: 'post',
url: CREATE_PAYMENT_URL,
headers: {
authorization: token
}
})
.then(function(data) {
return data.id;
});
};
let onAuthorize = (data) => {
return paypal.request({
method: 'post',
url: EXECUTE_PAYMENT_URL,
headers: {
authorization: token
},
json: {
paymentID: data.paymentID,
payerID: data.payerID
}
}).then(function() {
// The payment is complete!
// You can now show a confirmation message to the customer
console.log('done');
});
};
return (
<div>
<Btn env={'sandbox'}
client={client}
payment={payment}
commit={true}
onAuthorize={onAuthorize}
/>
</div>
);
}
My server side implementation):
module.exports = {
createPayment(req, res, next) {
const { registration_id } = req.params;
const { user } = req;
Registration.findById(registration_id)
.populate({ path: 'category', model: 'category' })
.populate({ path: 'orders.meal', model: 'meal' })
.then(reg => {
if (reg) {
const categoryItem = [{
name: reg.category.name,
sku: reg.category.name,
price: reg.category.price,
currency: 'MYR',
quantity: 1
}];
const ordersItems = reg.orders.map(order => {
return {
name: order.meal.name,
sku: order.meal.name,
price: order.meal.price,
currency: 'MYR',
quantity: order.quantity
}
});
const create_payment_json = {
intent: 'sale',
payer: {
payment_method: 'paypal'
},
redirect_urls: {
return_url: 'http://localhost:8080/',
cancel_url: 'http://localhost:8080/'
},
transactions: [{
item_list: {
items: [...categoryItem, ...ordersItems]
},
amount: {
currency: 'MYR',
total: reg.totalBill
}
}]
};
paypal.payment.create(create_payment_json, function(err, payment) {
if (err) {
next(err);
} else {
res.send(payment);
}
})
} else {
return res.status(422).send({error: 'Registration not found'});
}
})
.catch(next);
},
executePayment(req, res, next) {
const { registration_id } = req.params;
const { paymentID, payerID } = req.body;
const execute_payment_json = {
payer_id: payerID
}
paypal.payment.execute(paymentID, execute_payment_json, function(err, paypalResponse) {
if (err) {
next(err);
} else {
if (paypalResponse.state === 'approved') {
const payment = new Payment({
user: req.user._id,
registration: registration_id,
amount: paypalResponse.transactions[0].amount.total,
currency: paypalResponse.transactions[0].amount.currency,
paypalPaymentId: paypalResponse.id
});
payment.save()
.then(p => res.send(p))
.catch(next);
} else {
res.status(422).send({ error: 'Payment not approved' });
}
}
});
}
};
What is the issue? How can I make this work?
I have faced same issue.
I have set INR as my currency in my paypal sandbox, but sending amount as USD from API call.
So, I think we have to make transaction in currency that we set in sandbox.
Previous Payload:
transactions: [{
item_list: {
items: [...categoryItem, ...ordersItems]
},
amount: {
currency: 'USD',
total: reg.totalBill
}
}]
After Change Payload:
transactions: [{
item_list: {
items: [...categoryItem, ...ordersItems]
},
amount: {
currency: 'INR',
total: reg.totalBill
}
}]
After changing currency, my code works fine !!