I am adding data to the realtime database with React JS, a contact form and the firebase initialized. That all works.
However, I'm trying to implement an email to be sent to me when a new contact form has been submitted. CURRENT PROBLEM: The cloud function is deployed yet when I submit the form (and realtime db is added to), nothing happens. Not even an error message in the firebase console.
Please can you take a look at my code and offer some advice as to how I can get the automatic emails sent.
const functions = require('firebase-functions')
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
admin.initializeApp()
require('dotenv').config()
const email = process.env.REACT_APP_SENDER_EMAIL;
const pass = process.env.REACT_APP_SENDER_PASS;
exports.sendEmailNotification = functions.firestore.document('messages/{id}')
.onCreate((snap, ctx) => {
const data = snap.data();
let authData = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: email,
pass: pass
}
});
authData.sendMail({
from: data.email,
to: data.to,
subject: data.name + ' sent a message',
text: data.text,
}).then(res => console.log('email sent')).catch(err => console.log(err));
});
Your function needs to return a promise that resolves when all the asynchronous work is complete.
return authData.sendMail({
from: data.email,
to: data.to,
subject: data.name + ' sent a message',
text: data.text,
})
Returning this promise lets Cloud Functions know when it's safe to clean up and move on.
I first attempted creating this as a firebase cloud function as well, but I shifted towards building nodemailer on the server. Working in firebase cloud functions I was using the loophole of downgrading to node: 8 in package.json (which is deprecated) and I was being forced into making a Google firebase paid plan. Both items were driving me into a corner that I didn't want to be in.
This is the result of nodemailer in node.js thanks to https://www.youtube.com/watch?v=nF9g1825mwk
const express = require('express')
require('dotenv').config()
const bodyParser = require('body-parser')
const exphbs = require('express-handlebars')
const path = require('path')
const nodemailer = require('nodemailer')
const app = express()
const email_from = process.env.EMAIL_FROM;
const sender_pass = process.env.SENDER_PASS;
const email_to = process.env.EMAIL_TO;
// View engine setup
app.engine('handlebars', exphbs())
app.set('view engine', 'handlebars')
//body parser
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
//static folder
app.use('/public', express.static(path.join(__dirname, 'public')))
app.get('/', (req, res) => {
res.render('contact', { layout: false })
})
app.post('/send', (req, res) => {
const output = `
<p>You have a new submission</p>
<h3>Contact Details</h3>
<ul>
<li>Name: ${req.body.name}</li>
<li>Company: ${req.body.company}</li>
<li>Email: ${req.body.email}</li>
<li>Phone: ${req.body.phone}</li>
</ul>
<h3>Message</h3>
<p> ${req.body.message} </p> `;
async function main() {
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
// let testAccount = await nodemailer.createTestAccount();
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: email_from,
pass: sender_pass
},
});
// send mail with defined transport object
let info = await transporter.sendMail({
from: email_from,
to: email_to,
subject: 'New Submission from Dean Productions!',
text: 'new submission',
html: output,
});
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...
res.render('contact', { layout: false, msg: 'Message has been sent!' })
}
main().catch(console.error);
})
app.listen(3000, () => console.log('Server started...'))
Related
///component
function Home() {
const [show, setShow]= useState([{name:'', info:'', airingDate:'', poster:''}])
useEffect(()=>{
fetch("/home")
//.then(res=> res.json())
.then(res => res.text())
.then(text => console.log(text))
})
return (
<div>
{show.map(a=>
<div>
<h2>{a.title}</h2>
</div>
)}
</div>
)
/////index.js
const TvShows = require("./models/TvShows");
const express = require("express");
const app = express();
const mongoose= require("mongoose")
const dotenv= require("dotenv");
const authRoute = require("./routes/auth");
const { application } = require("express");
const userRoute = require("./routes/users");
const commentRoute = require("./routes/comments");
const tvshowsRoute = require("./routes/tvshows");
const cors = require("cors");
app.use(cors());
console.log(".");
dotenv.config();
app.use(express.json());
mongoose.connect(process.env.MONGO_URL,{
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
}).then(console.log("connected to mongoDB"));
app.use("/auth", authRoute);
app.use("/users", userRoute);
app.use("/comments", commentRoute);
app.post("/api/home", tvshowsRoute);
app.use("/api/home", tvshowsRoute);
/*
app.get('/api/home', (req,res)=>{
TvShows.find().then((result)=>{
res.send(result);
})
})
*/
/*
app.use("/",(req,res)=>{
console.log("main url")
})*/
app.listen("3001",()=>{
console.log("backend running");
})
//////route
const router = require("express").Router();
const TvShows = require("../models/TvShows");
router.post("/api/home", async (req, res) => {
console.log("here")
try{
const newTvShow = new TvShows({
title: req.body.title,
poster: req.body.poster,
info: req.body.info
});
const savedTvShows = await newTvShow.save();
res.status(200).json(savedTvShows);
}catch (err) {
res.status(500).json(err);
}
}
);
router.route("/api/home").get((req, res)=>{
TvShows.find()
.then(foundShows=> res.json(foundShows))
})
module.exports = router;
when I change res.json with res.text I see my index.html page on console not the data I want to fetch from mongodb. This error is probably because I didn't use /api/ on root url but I couldn't figure it out where I should write it. I tried but didn't work. It would be so good if someone could've helped. Thank you so much.
Indeed, you are fetching the /home page of your front-end app.
Assuming the api is on a different server, you would need to call the address of that server.
If you have a set up locally with a nodejs server and a react app running separately, you should have them run on two different ports.
If you have react app on http://localhost:3000 (default), then change your api to listen on 3001, then in your react code above, you can use the full uri
http://localhost:3001/api/home
in your fetch call.
I'm making a lot of assumptions about how you have this set up, based on my own experience of local development for similar problems.
I'm using Google Spreadsheets as a lightweight database for my React App. The app is a wedding website and the idea is for users to be able to RSVP and the response gets saved in a Google Spreadsheet. See GitHub for a visual representation of what I mean: https://github.com/michelleroos/mimirgettingmarried
It's working as intended on desktop, meaning RSVP responses get saved to the sheet, but not on mobile and I can't figure out why.
Would love your input! If I could even just understand where to start looking that would be great but there is not a lot of information out there on Google Spreadsheet Api.
const express = require("express");
const app = express();
const { google } = require('googleapis');
const PORT = process.env.PORT || 3001; // use live port or the declared one
var cors = require('cors');
app.use(cors())
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const auth = new google.auth.GoogleAuth({
keyFile: "./config/credentials.json",
scopes: 'https://www.googleapis.com/auth/spreadsheets',
});
const spreadsheetId = "1-R0vjBCOD5hcipk9AQbVUwQAH_0dFvQB2qs5gxsxWg0";
const range = "rsvp";
// api endpoint returns a CB. We decide what to do w res thru CB
app.get("/api/rsvp", async (req, res) => {
// create client instance for auth
const client = await auth.getClient();
// create Google sheets API instance
const googleSheets = google.sheets({ version: "v4", auth: client })
// read rows
const getRows = await googleSheets.spreadsheets.values.get({
auth,
spreadsheetId,
range,
});
// express/app sends to the client (the body)
res.send(getRows.data.values);
});
app.post("/api/rsvp", async (req, res) => {
// create client instance for auth
const client = await auth.getClient();
// create Google sheets API instance
const googleSheets = google.sheets({ version: "v4", auth: client })
const writeRows = await googleSheets.spreadsheets.values.append({
auth,
spreadsheetId,
range,
valueInputOption: "USER_ENTERED", // parses info
resource: {
values: [
// Object.keys(req.body), // header
Object.values(req.body) // values
]
}
})
res.send(writeRows);
});
app.listen(PORT, () => {
console.log(`Server listening on ${PORT}`);
});
in my React-App, I want to send an email with axios and nodemailer to the user after registration. So, when the button "Register" is pushed, it will use the api "/api/form" for sending the data to the server.
My problem is that the data will not be sent to the server in normal using. If I use the developertools with breackpoints for debugging this function, it works! So it can not be a problem with the server side, I think the problem is at the front end, but I don´t see why.
Is it the way I call axios.post?
Here is the code for the submit function (front end):
async onSubmit(e) {
e.preventDefault
var token = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
const { firstName, email, authToken } = this.state;
const form = await axios.post('/api/form', {
firstName,
email,
authToken: token
}, console.log("form"),this.props.history.push(`/login`));
return form;
}
This is the code in index.js (server):
/*Double-Opt-In*/
app.use(express.static('public'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
/*Double-Opt-In*/
app.post('/api/form', (req, res) => {
console.log(req.body);
nodemailer.createTestAccount((err, account) => {
token= req.body.authToken;
link="http://localhost:3000/verify"+"?token="+token;
console.log("createTestAccount");
const htmlEmail = `
<h3>WebFit Registrierung</h3>
`
let transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: 'email',
pass: 'password'
}
})
let mailOptions = {
from: 'WebFit', // sender address
to: req.body.email, // list of receivers
replyTo: 'app#webfit.app',
subject: 'Welcome to Webfit <3', // Subject line
text: req.body.message, // plain text body
html: htmlEmail // html body
};
// send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Message sent: %s', info.messageId);
// Preview only available when sending through an Ethereal account
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321#example.com>
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
});
});
});
const PORT = process.env.PORT || 3001
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
})
/*End Double-Opt-In*/
axios runs fine with async/await, so catch the throw/reject and check if axios or other. Depending on your api you can extract further info like shown:
async function login(token) {
/* post token and return jwt */
try {
const d = await api.post("/login", token);
return res.data["jwt"];
} catch (e) {
if(e.name == 'AxiosError'){
// map axios error to an app-friendly display message
// your api error handling dictates what you pull out,
// expressjs: return res.status(401).send("expired.");
// results in: '401 expired.'
return new Error(`${e.response.status} ${e.response.data}`);
} else {
return new Error(`${e.name} ${e.message}`);
}
}
}
If your tired of handling on every api call, use interceptors which can do the api error 2 local app error mapping service.js wide.
I have a form, which submits name, email and text.
onSubmit function of the React component:
onSubmit = e => {
const { name, email, text } = this.state;
axios.post('/feedback', { name, email, text })
.then((result) => {
console.log(result)
}).catch(err => console.log(err))
}
feedback.js file (api/feedback.js - this works fine and sends email if requested via Postman):
const express = require("express");
const router = express.Router();
const nodemailer = require("nodemailer");
// #route POST api/feedback
// #desc Tests resource route
// #access Public
router.post("/", function(req, res, next) {
let output = `<p>New feedback</p>
<h3>Feedback details</h3>
<ul>
<li>Name: ${req.body.name}</li>
<li>Email: ${req.body.email}</li>
</ul>
<h3>Feedback message</h3>
<p>${req.body.text}</p>
`;
const transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
auth: {
user: "t4qj6mgea2kpyep7#ethereal.email",
pass: "PASSWORD"
},
tls: {
rejectUnathorized: false
}
});
let mailOptions = {
from: 'Webtool feedback: <t4qj6mgea2kpyep7#ethereal.email>', // sender address
to: "TO#EMAIL.COM", // list of receivers
subject: 'Feedback from Webtool', // Subject line
text: 'Hello world', // plain text body
html: output // html body
};
// send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log("Message sent: %s", info.messageId);
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
});
});
module.exports = router;
I have imported this into App.js, and set up the route for the feedback:
const feedback = require('./routes/api/feedback');
The issue is that the endpoint itself works, if I use Postman, I successfully receive an e-mail, so I don't suspect the feedback.js file. However, onSubmit() doesn't work.
Did you make sure to include the middleware to protect web servers?
--Try this--
//middleware meant to protect web servers [CORS requests]
//can change '*' to 'http://localhost:3000' for local host testing
app.use((request, response, next) => {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "Content-Type");
next();
app.use(bodyParser.json())
app.use(bodyParser.urlencoded){
extended: false
}));
You are sending the form to /feedback as a post request but in the server side you you are having a post method with route /.So,The route /feedback is not in the express and so it throwed you 404 not found.Try changing the server route to /feedback.
So I'm following Feathersjs docs regarding authentication,
and I have a middleware /signup,
from User Management docs:
module.exports = function(app) {
return function(req, res, next) {
const body = req.body;
app.service('users').create({
email: body.email,
password: body.password
})
// Then redirect to the login page
.then(user => res.redirect('/login.html'))//this will be a redirect in my client not in the server
.catch(next);
};
};
Now in src/middleware/index.js: I have :
module.exports = function() {
const app = this;
app.post('/signup', signup(app));// how can I reimplement this with sockets
app.use(notFound());
app.use(logger(app));
app.use(handler());
};
Using REST was easy :
request.post(`${SERVER}/signup`)
.send({ email: username, password: password })
.then(data=>{console.log(`data comming from response`,data)})
.catch(error=>{console.log(`ERROR comming from response`,error)})
so the problem is that now that I'm using sockets (feathers-client) I don't know how to tell feathers client to "post" the email/pass to that /signup middleware. Is there any way to achieve this?
this is my client conf:
import feathers from 'feathers-client';
const io = require('socket.io-client');
var socket = io(SERVER);
let feathersClient =
feathers()
.configure(feathers.socketio(socket))
.configure(feathers.hooks())
.configure(feathers.authentication({
storage: window.localStorage
}));
You don't need the signup middleware. Just create a new user through the /users service on your client like this:
import feathers from 'feathers-client';
const io = require('socket.io-client');
var socket = io(SERVER);
let feathersClient =
feathers()
.configure(feathers.socketio(socket))
.configure(feathers.hooks())
.configure(feathers.authentication({
storage: window.localStorage
}));
feathersClient.service('users').create({
email: 'test#example.com',
password: 'testing'
});
Then you will be able to authenticate the user like this:
feathersClient.authenticate({
type: 'local',
email: 'test#example.com',
password: 'testing'
});