Setup mailgun with parse-server on Heroku - parse-server

I am trying to set up mailgun for use with parse-server app on Heroku.
The recommended API is
https://github.com/ParsePlatform/parse-server-simple-mailgun-adapter
But instructions on how to actually achieve this are non existent.
--------- EDIT -----------
Followed instructions and server pushes to Heroku. Though i am still not receiving emails. Im just using mailgun sandbox and have authorised and activated a recipient.
Im not sure about specifying the path to templates or:
fromAddress: process.env.EMAIL_FROM
This wasn't supplied by mailgun so i have just entered no-reply#myservername.heroku
This obviously isnt valid?
index.js code:
var express = require('express');
var ParseServer = require('parse-server').ParseServer;
var S3Adapter = require('parse-server').S3Adapter;
var path = require('path');
const resolve = require('path').resolve;
var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;
if (!databaseUri) {
console.log('DATABASE_URI not specified, falling back to localhost.');
}
var api = new ParseServer({
//**** General Settings ****//
databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js',
serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse', // Don't forget to change to https if needed
maxUploadSize: '500mb',
//**** Security Settings ****//
// allowClientClassCreation: process.env.CLIENT_CLASS_CREATION || false,
appId: process.env.APP_ID || 'myAppId',
masterKey: process.env.MASTER_KEY || 'myMasterKey', //Add your master key here. Keep it secret!
//**** Live Query ****//
// liveQuery: {
// classNames: ["TestObject", "Place", "Team", "Player", "ChatMessage"] // List of classes to support for query subscriptions
// },
//**** File Storage ****//
filesAdapter: new S3Adapter({
accessKey: process.env.S3_ACCESS_KEY || '',
secretKey: process.env.S3_SECRET_KEY || '',
bucket: process.env.S3_BUCKET || '',
directAccess: true
}),
//**** Email Verification ****//
/* Enable email verification */
// verifyUserEmails: true,
/* The public URL of your app */
// This will appear in the link that is used to verify email addresses and reset passwords.
publicServerURL: process.env.SERVER_URL || '',
appName: process.env.APP_NAME || '',
emailAdapter: {
module: 'parse-server-mailgun',
options: {
fromAddress: process.env.EMAIL_FROM || '',
domain: process.env.MAILGUN_DOMAIN || '',
apiKey: process.env.MAILGUN_API_KEY || '',
templates: {
passwordResetEmail: {
subject: 'Reset your password',
pathPlainText: resolve(__dirname, 'https:/myparseserveronheroku.herokuapp.com/node_modules/parse-server-mailgun-adapter/test/email-templates/password_reset_email.txt'),
pathHtml: resolve(__dirname, 'https:/myparseserveronheroku.herokuapp.com/node_modules/parse-server-mailgun-adapter/test/email-templates/password_reset_email.html'),
callback: (user) => { return { firstName: user.get('firstName') }}
// Now you can use {{firstName}} in your templates
},
verificationEmail: {
subject: 'Confirm your account',
pathPlainText: resolve(__dirname, 'https:/myparseserveronheroku.herokuapp.com/node_modules/parse-server-mailgun-adapter/test/email-templates/verification_email.txt'),
pathHtml: resolve(__dirname, 'https:/myparseserveronheroku.herokuapp.com/node_modules/parse-server-mailgun-adapter/test/email-templates/verification_email.html'),
callback: (user) => { return { firstName: user.get('firstName') }}
// Now you can use {{firstName}} in your templates
}
}
}
}
});
My swift code to send password reset says that the message has been sent, so i assume that everything Cliff has told me is correct and the server side is set up ok, probably just something stupid that i have done with the variables.
PFUser.requestPasswordResetForEmail(inBackground: emailAddress!) { (success, error) in
if error != nil {
// display error message
let userMessage: String = error!.localizedDescription
GeneralFunctions.createAlert(errorTitle: "Oops...", errorMessage: userMessage, className: self )
} else {
// display success message
let userMessage: String = "An new password was sent to \(emailAddress!)"
GeneralFunctions.createAlert(errorTitle: "Hooray...", errorMessage: userMessage, className: self )
}
}
----------- EDIT ----------
Verbose log after trying reset password
email=my-verified-email#email.com.au
2017-02-06T00:44:25.788619+00:00 app[web.1]: [36mverbose[39m: RESPONSE from [POST] /parse/requestPasswordReset: {
2017-02-06T00:44:25.788623+00:00 app[web.1]: "response": {}
2017-02-06T00:44:25.788625+00:00 app[web.1]: }
2017-02-06T00:44:25.797455+00:00 app[web.1]: { Error: ENOENT: no such file or directory, open '/app/https:/myparseserveronheroku.herokuapp.com/node_modules/parse-server-mailgun-adapter/test/email-templates/password_reset_email.txt'
2017-02-06T00:44:25.797458+00:00 app[web.1]: errno: -2,
2017-02-06T00:44:25.797459+00:00 app[web.1]: code: 'ENOENT',
2017-02-06T00:44:25.797459+00:00 app[web.1]: syscall: 'open',
2017-02-06T00:44:25.797462+00:00 app[web.1]: path: '/app/https:/myparseserveronheroku.herokuapp.com/node_modules/parse-server-mailgun-adapter/test/email-templates/password_reset_email.txt' }
2017-02-06T00:44:25.792191+00:00 heroku[router]: at=info method=POST path="/parse/requestPasswordReset" host=myparseserveronheroku.herokuapp.com request_id=666ff3e0-db4a-4e76-b7b5-6353edc7e15a fwd="111.111.111.11" dyno=web.1 connect=0ms service=81ms status=200 bytes=483
So definitely looks like its trying to send from a template that doesn't exist. Im not sure how to specify path here, i had thought that all these directories including node_modules/parse-server-mailgun-adapter/test/email-templates were pushed to Heroku.

First:
npm install --save parse-server-mailgun
Then in your index.js file you can set it up as follows in the initialize:
publicServerURL: 'http://MY_HEROKU_APP.herokuapp.com/parse',
appName: 'MY_APP',
emailAdapter: {
module: 'parse-server-mailgun',
options: {
fromAddress: 'no-reply#example.com',
domain: 'example.com',
apiKey: 'key-XXXXXX',
}
}
The default Mailgun adapter that comes with the Parse Server, you need to set a fromAddres, and the domain and apiKey provided by Mailgun. In addition, you also need to configure the templates you want to use. You must provide at least a plain-text version for each template. The html versions are optional.
verifyUserEmails: true,
emailAdapter: {
module: 'parse-server-mailgun',
options: {
// The address that your emails come from
fromAddress: 'YourApp <noreply#yourapp.com>',
// Your domain from mailgun.com
domain: 'example.com',
// Your API key from mailgun.com
apiKey: 'key-mykey',
// The template section
templates: {
passwordResetEmail: {
subject: 'Reset your password',
pathPlainText: resolve(__dirname, 'path/to/templates/password_reset_email.txt'),
pathHtml: resolve(__dirname, 'path/to/templates/password_reset_email.html'),
callback: (user) => { return { firstName: user.get('firstName') }}
// Now you can use {{firstName}} in your templates
},
verificationEmail: {
subject: 'Confirm your account',
pathPlainText: resolve(__dirname, 'path/to/templates/verification_email.txt'),
pathHtml: resolve(__dirname, 'path/to/templates/verification_email.html'),
callback: (user) => { return { firstName: user.get('firstName') }}
// Now you can use {{firstName}} in your templates
},
customEmailAlert: {
subject: 'Urgent notification!',
pathPlainText: resolve(__dirname, 'path/to/templates/custom_alert.txt'),
pathHtml: resolve(__dirname, 'path/to/templates/custom_alert.html'),
}
}
}
Reference to the NPM package: https://www.npmjs.com/package/parse-server-mailgun

Related

Unable to authenticate a user using #hapi/cookie 19.x.x

I've recently upgraded my project to use hapi 19.x.x along with that I have updated the project to use #hapi/cookie as opposed to the deprecated hap-auth-cookie however after successful authentication my application constantly tries to reauthenticate even after setting a session cookie with request.cookieAuth.set({ id : id})
When the application is redirected to the 'restricted page' using the redirectTo: property on the .auth.strategy('admin', 'cookie', {}) object.
I noticed that the state on the incoming request is {} empty when it shouldn't be
node -v // 12.16.2
Google Chrome
Version 80.0.3987.163 (Official Build) (64-bit)
package.json {
"dependencies": {
"#hapi/catbox-redis": "5.0.5",
"#hapi/cookie": "11.0.1",
"#hapi/h2o2": "9.0.1",
"#hapi/hapi": "19.1.1",
"#hapi/inert": "6.0.1",
"#hapi/joi": "17.1.1",
"#hapi/scooter": "6.0.0",
"#hapi/wreck": "17.0.0",
}
server.auth.strategy('admin', 'cookie', {
cookie: {
name: Server.cookieName,
password: auth_cookie_password,
isSecure: false,
ttl: Server.cacheCookieTtlMs
},
appendNext: true,
redirectTo: outboundUrl,
validateFunc: async (request: any, session: any) => {
// blah blah
}
{
method: ['GET', 'POST'],
path: '/login',
options: {
auth: false,
security: true
},
handler: async (request: any, h) => {
try {
const tokenSet = await authCallback();
const session = {
id: tokenSet.id,
}
request.cookieAuth.set(session);
const returnScript = `<script type="application/javascript" >(function() { setTimeout(function() {window.location = "http://localhost:3000"})})()</script>`;
return h.response(returnScript)
} catch (e) {
return h.response('Internal server error').code(500)
}
}
}
any help would be appreciated.
you have to set the cookie path to /
Cookies are only sent to the server when the URL of the request starts with the value of the cookie’s path. When you omit path, the default is the URL of the request that received the response with the Set-Cookie header. So, let’s say you omit path and your cookie is set on a URL like https://example.com/login (which is very common), then the cookie will only be sent on requests for subpaths like https://example.com/login/foo, which is almost never what you want.

Haste module 'nodemailer' does not exist in haste module map

Im building a mobile app from the beonews pro template. I needed to add in some email functionality for a contact form. So naturally I did npm install nodemailer only to see the following error.
Haste module 'nodemailer' does not exist in haste module map
It lists off the four steps to follow
1.watchman watch-del-all
2.rm -rf node_module && yarn
3.rm -rf /tmp/metro-bundler-cache-*
4.rm -rf /tmp/haste-map-react-native-packager-*
After following the above steps I still receive the same error.
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { View, Text, ScrollView, TextInput, Button, Image, Animated } from 'react-native'
import wp from '#services/WPAPI'
import WebView from '#components/WebView/WebView'
import AnimatedButton from '#components/AnimatedButton/index'
import { Toolbar } from '#components'
import styles from './styles'
import nodemailer from 'nodemailer';
export default class CustomPage extends PureComponent {
static propTypes = {
id: PropTypes.any,
}
constructor(props) {
super(props)
this.state = {
newsletterFName: '',
newsletterLName: '',
newsletterEmail: '',
contactFName: '',
contactLName: '',
contactEmail: '',
contactMessage: '',
loading: false,
success: false
}
}
onSubmitNewsletter() {
console.log('the form has been submitted');
this.setState({
loading: true
})
fetch('https://us18.api.mailchimp.com/3.0/lists/dad57ba7fe/members', {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, cors, *same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer', // no-referrer, *client
body: JSON.stringify({
"email_address": this.state.newsletterEmail,
"status": "subscribed",
"merge_fields": {
"FNAME": this.state.newsletterFName,
"LNAME": this.state.newsletterLName
}
}), // body data type must match "Content-Type" header
})
.then(response => {
console.log('response before timeout', response);
setTimeout(() => {
console.log('inside timout')
if(response.status !== 200) {
this.setState({
loading: false,
})
} else {
this.setState({
loading: false,
newsletterEmail: '',
newsletterFName: '',
newsletterLName: '',
success: true
})
}
}, 2000);
});
}
onSubmitContactForm() {
console.log('contact form submitted');
let email = this.state.contactEmail;
let first = this.state.contactFName;
let last = this.state.contactLName;
let msg = this.state.contactMessage;
// async..await is not allowed in global scope, must use a wrapper
async function main(){
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "smtp.office365.com",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: 'xxx', // generated ethereal user
pass: 'xxx' // generated ethereal password
}
});
// send mail with defined transport object
let info = await transporter.sendMail({
from: '"Fred foos" <foo#example.com>', // sender address
to: this.state.contactEmail, // list of receivers
subject: 'News.Law Mobile Contact Form', // Subject line
text: this.state.contactMessage, // plain text body
html: this.state.contactMessage // html body
});
console.log("Message sent: %s", info.messageId);
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321#example.com>
// Preview only available when sending through an Ethereal account
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
}
main().catch(console.error);
}
}
I expect the package to load so I can send an email based of contact form submission. actual results gets me an error described above.
In this instance what worked for me was to make sure I restarted the computer after following the four steps. I think it may have had something to do with the simulator? But am unsure.

Send email using Nodemailer with GoDaddy hosted email

I am trying to send an email using nodemailer and a custom email address configured through GoDaddy. Here is a screen shot of the "custom configurations" page in c-panel:
and my code:
const nodemailer = require('nodemailer');
var transporter = nodemailer.createTransport({
service: 'Godaddy',
secureConnection: false,
auth: {
user: 'info#mywebsite.com',
pass: 'mypassword'
}
});
var mailOptions = {
from: 'info#mywebsite.com',
to: 'otheremail#gmail.com',
subject: 'Sending Email using Node.js',
text: 'That was easy!',
html: '<h1>Welcome</h1><p>That was easy!</p>'
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
and my error log:
{ Error: connect EHOSTUNREACH 173.201.192.101:25
at Object.exports._errnoException (util.js:1012:11)
at exports._exceptionWithHostPort (util.js:1035:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1080:14)
code: 'ECONNECTION',
errno: 'EHOSTUNREACH',
syscall: 'connect',
address: '173.201.192.101',
port: 25,
command: 'CONN' }
I've tried changing the port number, making it secure vs non-ssl, using my website address as the host, and pretty much everything else I can think of. I have successfully sent an email from the godaddy email using one of the webmail clients. Has anyone else ever encountered this or have recommendations on things to try?
I am trying to send emails using nodemailer from Google Cloud Function using GoDaddy SMTP settings. I do not have Office365 enabled on my GoDaddy hosting. None of the above options worked for me today (12 November 2019). TLS need to be enabled.
I had to use the following configuration:
const mailTransport = nodemailer.createTransport({
host: "smtpout.secureserver.net",
secure: true,
secureConnection: false, // TLS requires secureConnection to be false
tls: {
ciphers:'SSLv3'
},
requireTLS:true,
port: 465,
debug: true,
auth: {
user: "put your godaddy hosted email here",
pass: "put your email password here"
}
});
Then, I could send a test email as follows:
const mailOptions = {
from: `put your godaddy hosted email here`,
to: `bharat.biswal#gmail.com`,
subject: `This is a Test Subject`,
text: `Hi Bharat
Happy Halloween!
If you need any help, please contact us.
Thank You. And Welcome!
Support Team
`,
};
mailTransport.sendMail(mailOptions).then(() => {
console.log('Email sent successfully');
}).catch((err) => {
console.log('Failed to send email');
console.error(err);
});
you should make some changes in your transporter:
var smtpTrans = nodeMailer.createTransport({
service: 'Godaddy',
host: "smtpout.secureserver.net",
secureConnection: true,
port: 465,
auth: {
user: "username",
pass: "password"
}
});
I realize this is an old post, but just wanted to add to this since the GoDaddy SMTP server has changed, just in case someone else comes across this and has the same problem I had. The answer by #tirmey did not work for me, but this did.
let nodemailer = require('nodemailer');
let mailerConfig = {
host: "smtp.office365.com",
secureConnection: true,
port: 587,
auth: {
user: "username#email.com",
pass: "password"
}
};
let transporter = nodemailer.createTransport(mailerConfig);
let mailOptions = {
from: mailerConfig.auth.user,
to: 'SomePerson#email.com',
subject: 'Some Subject',
html: `<body>` +
`<p>Hey Dude</p>` +
`</body>`
};
transporter.sendMail(mailOptions, function (error) {
if (error) {
console.log('error:', error);
} else {
console.log('good');
}
});
Solutions proposed above seem no longer valid, none of them worked for me. Following solution works for me:
const nodemailer = require('nodemailer');
const os = require('os');
let mailerConfig = {
host: os.hostname(),
port: 25,
};
let transporter = nodemailer.createTransport(mailerConfig);
transporter.sendMail({
from: '<from>',
to: '<to>',
subject: '<subject>',
text: '<text>'
}, (err, info) => {
console.log(info);
console.log(err);
});
I could solve the problem by using this code and some points that I brought them after codes:
const smtpTransport = nodemailer.createTransport({
host: "smtp.office365.com",
secure: false,
port: 587,
auth : {
user : 'info#my-domain.com',
pass : 'Password'
}
});
const mailOptions = {
to: 'target-mail#',
subject: 'Test 01',
html: 'Body',
from : 'info#resoluship.com'
};
await smtpTransport.sendMail(mailOptions);
Don't forget to use 'from' attribute in mailOptions
Don't use ',' in your 'from' attribute
For me, the solution for production shared hosting server was completely different than for testing.
It seems no authentication or credentials are required for it to work.
I created this code from this document describing how to use an SMTP relay server. You can use this with nodemailer. GoDaddy support told me I couldn't but I don't think they know about third party tools.
https://au.godaddy.com/help/send-form-mail-using-an-smtp-relay-server-953
async function main() {
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: 'localhost', //use localhost for linux cPanel hosting
port: 25,
secure: false,
// no need for authentication
tls: {
rejectUnauthorized: false
}
});
// send mail with defined transport object
let info = await transporter.sendMail({
to: "you#youremail.com", // list of receivers
subject: `New Message from ${name}`, // Subject line
text: `yourtext`, // plain text body
html: `your text in html`, // html body
headers: {
priority: 'high'
},
from: "you#youremail.com" // sender address
});
// send success page if successful
if (res.statusCode === 200) {
res.sendFile(path.join(__dirname, 'views/success.ejs'))
}
console.log("Message sent: %s", info.messageId, res.statusCode);
}
main().catch(console.error);
The most common problem with this error is the antivirus. So disable it for 10 minutes if you are testing it locally.

How can I override builtin login method in Loopback?

I've created a new User model, based on builtin one. I'm trying this:
module.exports = function(TiUser) {
TiUser.on('dataSourceAttached', function(obj) {
var login = TiUser.login;
TiUser.login = function(credentials, include, cb) {
var result = login.apply(this, credentials);
// Do my stuff
cb(null, my_data);
};
});
};
But I can't get it working... What is wrong? or how could this be done right?
Thanks
You may want to consider adding an afterRemote() hook to login(). Now you can achieve to add role( using Role model ) to user. For example:
TiUser.afterRemote('login', function(ctx, next) {
//add role to the user.
next();
});
At the end I've created a new method instead of overriding a current one:
module.exports = function(TiUser) {
TiUser.auth = function(credentials, include, fn) {
var self = this;
self.login(credentials, include, function(err, token) {
authInfo = {
token: token
};
fn(err, authInfo);
});
};
TiUser.remoteMethod(
'auth',
{
description: 'Login method with Role data information embedded in return',
accepts: [
{arg: 'credentials', type: 'object', required: true, http: {source: 'body'}},
{arg: 'include', type: ['string'], http: {source: 'query' },
description: 'Related objects to include in the response. ' +
'See the description of return value for more details.'}
],
returns: {
arg: 'accessToken', type: 'object', root: true,
description: 'User Model'
},
http: {verb: 'post'}
}
);
};

Sending Templated emails with node.js, node mailer and nodemailer-mailgun-transport

I have the following basic nodejs app:
var nodemailer = require('nodemailer');
var hbs = require('nodemailer-express-handlebars');
var options = {
viewEngine: {
extname: '.hbs',
layoutsDir: 'views/email/',
defaultLayout : 'template',
partialsDir : 'views/partials/'
},
viewPath: 'views/email/',
extName: '.hbs'
};
var mg = require('nodemailer-mailgun-transport');
var auth = {
auth: {
api_key: ' mailgun api key ',
domain: ' mailgun email domain '
}
}
var mailer = nodemailer.createTransport(mg(auth));
mailer.use('compile', hbs(options));
mailer.sendMail({
from: 'test#inventori.io',
to: 'test#test.com',
subject: 'Any Subject',
template: 'email.body',
context: {
variable1 : 'value1',
variable2 : 'value2'
}
}, function (error, response) {
// console.error(error);
if (error) {
throw error;
};
console.log('mail sent to ',response);
mailer.close();
});
views/email/template.hbs
{{>email/head}}
<body>
{{>email/header}}
{{{body}}}
{{>email/footer}}
</body>
</html>
views/email/email.body.hbs
<h4>Main Body Here</h4>
{{variable1}} <br/>
{{variable2}}
views/partials/email/header.hbs
<h4>Header Content</h4>
views/partials/email/footer.hbs
<h4>Footer Content</h4>
The handlebars template engine gives zero errors but the mailgun transport throws the following error:
Error: Sorry: template parameter is not supported yet. Check back soon!
at IncomingMessage.<anonymous> (~/test/node_modules/nodemailer-mailgun-transport/node_modules/mailgun-js/lib/request.js:228:15)
at IncomingMessage.emit (events.js:129:20)
at _stream_readable.js:908:16
at process._tickCallback (node.js:355:11)
This example uses the gmail node mailer transport:
http://excellencenodejsblog.com/express-nodemailer-sending-mails/
I would like to be able to send templated emails using mailgun.
Any help would be greatly appreciated.
Thank you.
Change your template parameter to html.
If you look at the source code here, the error correct- there is no such thing as a template parameter.
For me, templates weren't rendering properly because I was following an example using extName when the key was actually extname (all lowercase). Perhaps it was renamed overtime and the guide I was looking at is now somewhat out of date.
Full working example below as of 30 May 2020.
Directory Structure:
root/
src/
email-templates/
layouts/
blank.hbs
partials/
hello.hbs
services/
email.service.ts
email.service.ts (Haven't updated this to use proper types yet. Just a poc.)
export async function sendTestEmail() {
try {
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
const testAccount = await nodemailer.createTestAccount()
// create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
host: 'smtp.ethereal.email',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: testAccount.user, // generated ethereal user
pass: testAccount.pass, // generated ethereal password
},
})
transporter.use('compile', hbs({
viewEngine: {
extname: '.hbs', // handlebars extension
partialsDir: 'src/email-templates',
layoutsDir: 'src/email-templates/layouts',
defaultLayout: 'blank',
},
viewPath: 'src/email-templates',
extName: '.hbs'
}))
// send mail with defined transport object
const mailOptions = {
from: 'test#gmail.com', // sender address
to: 'test#gmail.com', // list of receivers
subject: 'Hello ✔', // Subject line
text: 'Hello world?', // plain text body
template: 'hello',
context: {
firstName: 'Clem'
}
}
const info = await transporter.sendMail(mailOptions)
console.log('Message sent: %s', info.messageId)
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321#example.com>
// Preview only available when sending through an Ethereal account
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info))
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
} catch (error) {
console.log(error)
}
}