Using: NextJS, Firebase (Auth, DB, etc...), Hosted on Vercel, OVH Domains, (Using next to for the backend (node))
Basically, went I send an email with Sendgrid library or with the API v3 directly, my mail got strange behavior.
When I send a mail to any address, I don't recieve de mail at all, I need to make multiple time THE SAME request to get the answer of the first, isn't that weird at all?
Like I send "1" to "joe#example.com", joe recieve nothing, I make the same request with "2", joe recieve nothing, then I make another "3", then finnally joe has recieved the first mail.
Then when I send other mail, I send the "4" I will recieve the "2", etc....
I feel like there is a Queue within my mail.
At first, if I'm not wrong the mail was always gapped by 1 when I used the library below. Then I moved to the API directly thinking it was a library issue.
Using the library
import { getAuth } from 'firebase-admin/auth'
import { supportConfig, websiteConfig, sendgridConfig } from 'src/config'
import sgMail from '#sendgrid/mail'
import path from 'path'
import fs from 'fs'
/**
* #name verifyEmail
* #description Send email verify link to user email
* #param {string} email
* #param {any} actionCodeSettings
* #returns Promise<Void>
*/
export default async function verifyEmail(
email: string,
actionCodeSettings: any
): Promise<void> {
const pathTemplate = path.join(
process.cwd(),
'src/api/lib/user/actionCode/emailTemplate/verifyEmail.html'
)
return await getAuth()
.generateEmailVerificationLink(email as string)
.then(async (link) => {
sgMail.setApiKey(sendgridConfig.apiKey as string)
fs.readFile(pathTemplate, 'utf8', async function (err, data) {
if (err) {
console.log(err)
throw err
}
const msg = {
to: [email, supportConfig.email as string],
from: supportConfig.email as string,
subject: "Email verification",
text: 'Please click on the link',
html: data.replace('{{link}}', link),
}
await sgMail
.send(msg)
.then(() => {
console.log('Email sent!')
})
.catch((error) => {
console.log(error)
throw error
})
})
})
.catch((error) => {
console.log(error)
throw error
})
}
Using the api directly
const message = {
personalizations: [
{
to: [
{
email: email,
},
],
},
],
from: {
email: 'support#nerap.fr',
name: 'Support',
},
replyTo: {
email: 'support#nerap.fr',
name: 'Support',
},
subject: 'Email verification',
content: [
{
type: 'text/html',
value: data.replace('{{link}}', link),
},
],
}
await axios({
method: 'post',
url: 'https://api.sendgrid.com/v3/mail/send',
headers: {
Authorization: `Bearer ${sendgridConfig.apiKey}`,
'Content-Type': 'application/json',
},
data: message,
})
Nothing fancy, it's like the template that Sendgrid give us.
Honestly I'm lost I don't have any lead to fix it.
Here come the crazy part, IT'S PERFECTLY WORKING ON LOCAL.
So I think my conclusion is this.
Vercel hosting might be limited or restricted by Sengrid this is the only rationnal way.
Like, I don't know, I took the priced version thinking it was a trick to force people to use the pay version.
I'm open to any suggestion thanks !
Thanks for sharing the code, it highlighted an asynchronous piece of code that was allowing your function to complete before it ran. On platforms like Vercel, when a function completes, the event loop is effectively suspended, but continues when the function runs again. In this case your code was not completing before the function ended, which is why later emails would trigger the email to be sent.
The asynchronous code that escaped was the use of fs.readFile within a promise. Calling on fs.readFile started the work to read the email template from the filesystem asynchronously, and would call the callback when done, which would then send the email. However, the execution of the function would continue and complete before that finished. Since you are using promises for everything else in this code, I'd recommend using fs/promises (with the line import { promises as fs } from 'fs') so that you can treat fs.readFile like the rest of your code. I rewrote your function below using the promise version, and hopefully this will work.
import { getAuth } from 'firebase-admin/auth'
import { supportConfig, websiteConfig, sendgridConfig } from 'src/config'
import sgMail from '#sendgrid/mail'
import path from 'path'
import { promises as fs } from 'fs'
/**
* #name verifyEmail
* #description Send email verify link to user email
* #param {string} email
* #param {any} actionCodeSettings
* #returns Promise<Void>
*/
export default async function verifyEmail(
email: string,
actionCodeSettings: any
): Promise<void> {
const pathTemplate = path.join(
process.cwd(),
'src/api/lib/user/actionCode/emailTemplate/verifyEmail.html'
)
return await getAuth()
.generateEmailVerificationLink(email as string)
.then(async (link) => {
sgMail.setApiKey(sendgridConfig.apiKey as string)
try {
const data = await fs.readFile(pathTemplate, 'utf8');
const msg = {
to: [email, supportConfig.email as string],
from: supportConfig.email as string,
subject: "Email verification",
text: 'Please click on the link',
html: data.replace('{{link}}', link),
}
await sgMail
.send(msg)
.then(() => {
console.log('Email sent!')
})
.catch((error) => {
console.log(error)
throw error
})
} catch(error) {
console.log(error)
throw error
}
})
.catch((error) => {
console.log(error)
throw error
})
}
You do have a confusing mix of then/catch and async/await in your code. I'd recommend using just one style in order to simplify things. With just async/await your code could look like this:
import { getAuth } from 'firebase-admin/auth'
import { supportConfig, websiteConfig, sendgridConfig } from 'src/config'
import sgMail from '#sendgrid/mail'
import path from 'path'
import { promises as fs } from 'fs'
/**
* #name verifyEmail
* #description Send email verify link to user email
* #param {string} email
* #param {any} actionCodeSettings
* #returns Promise<Void>
*/
export default async function verifyEmail(
email: string,
actionCodeSettings: any
): Promise<void> {
const pathTemplate = path.join(
process.cwd(),
'src/api/lib/user/actionCode/emailTemplate/verifyEmail.html'
)
try {
const auth = getAuth()
const link = await auth.generateEmailVerificationLink(email as string)
sgMail.setApiKey(sendgridConfig.apiKey as string)
const data = await fs.readFile(pathTemplate, 'utf8')
const msg = {
to: [email, supportConfig.email as string],
from: supportConfig.email as string,
subject: "Email verification",
text: 'Please click on the link',
html: data.replace('{{link}}', link),
}
await sgMail.send(msg)
console.log('Email sent!')
} catch(error) {
console.log(error)
throw error
}
}
Related
I got a nestjs application. It is listening on localhost:3000. I have health check, i can ping with curl or insomnia and it is working correctly. I can use localhost/3000/api/register to register a new user without any problem. I wanted to try it with sveltekit. And i had an issue when i tried to fetch data it and i got an error:
TypeError: fetch failed
at fetch (/Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/undici/index.js:105:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async send (/Users/marcelljuhasz/Development/svelte-kit-demo/src/lib/api.ts:16:13)
at async default (/Users/marcelljuhasz/Development/svelte-kit-demo/src/routes/register/+page.server.ts:23:15)
at async handle_action_json_request (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/page/actions.js:51:16)
at async resolve (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/index.js:356:17)
at async respond (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/index.js:229:20)
at async file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/exports/vite/dev/index.js:444:22
I checked my server i got the cors enabled. The front end is listening to: localhost:5173.
I have this code inside:
app.enableCors({
origin: 'http://localhost:5173',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
allowedHeaders: 'Content-Type, Accept',
preflightContinue: true,
});
I am learning sveltekit now and i checked a project inside the github repository to see how it is works but i read the documentations too:
https://github.com/sveltejs/realworld
The structure almost the same i have the lib folder with the api.ts
import { error } from '#sveltejs/kit';
const base = 'http://localhost:3000/api';
async function send({ method, path, data }) {
const opts = { method, headers: {} };
if (data) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(data);
}
const res = await fetch(`${base}/${path}`, opts);
if (res.ok || res.status === 422) {
const text = await res.text();
return text ? JSON.parse(text) : {};
}
console.log(res);
throw error(res.status);
}
export function get(path) {
return send({ method: 'GET', path });
}
export function del(path) {
return send({ method: 'DELETE', path,});
}
export function post(path, data) {
return send({ method: 'POST', path, data });
}
export function put(path, data) {
return send({ method: 'PUT', path, data });
}
I have a register.svelte in the routes dir. With +page.svelte and +page.server.ts is the same like in the repository, i just exclude my own fields. The data input is return in the correct format.
+page.server.ts looks like this, almost the same like in the repo:
import { fail, redirect } from '#sveltejs/kit';
import * as api from '$lib/api.js';
/** #type {import('./$types').PageServerLoad} */
export async function load({ parent }) {
const { user } = await parent();
if (user) throw redirect(307, '/');
}
/** #type {import('./$types').Actions} */
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const user = {
username: data.get('username'),
email: data.get('email'),
password: data.get('password')
};
const body = await api.post('register', { ...user });
if (body.errors) {
return fail(401, body);
}
console.log(body)
throw redirect(307, '/');
}
};
So in a nutshell i got typerror after i hit the sign uo button. On my server log tells nothing. I see this log in the sveltekit log. I tried to check cors, but it is okey and i haven't got any cors errors in dev console. I checked in my console with curl to check if is the server available. I tried to post, get with insomnia and curl. And it is worked as expected. I have no clue for this. It is wierd if i check the chrome dev tool the request. In the general tab the request url is: localhost:5173 which is the default vite.config for sveltekit server. But i passing my own server which is localhost:3000 and i dont understand what is this behavor. If anybody have experience with sveltekit i am curious what is wrong. I tried to fetch data with an own svelte file without +page.server.ts, i put this fetch method into the component file and it is worked. Wierd.
So im trying to build my register method without re-enventing nothing crazy with the create-t3-app stack with nextjs, trpc and nextauth:
export const signUpRouter = router({
signup: publicProcedure.input(UserModel).mutation(async ({ ctx, input }) => {
debugger;
try {
const { nickname, email, password } = input;
//check duplicate users
const checkingUsers = await ctx.prisma.user.findFirst({
where: {
email,
},
});
if (checkingUsers) {
return { message: "User already exists..." };
}
//hash password
return await ctx.prisma.user.create({
data: {
nickname,
email,
password: await hash(password, 12),
},
});
} catch (error: any) {
console.log("error", error);
throw new Error(error);
}
}),
});
export default signUpRouter;
This file is inside pages/api/auth/signup.ts
Should I have this on the server part ?
I have the router on my appRouter file
export const appRouter = router({
userLogin: userLoginRouter,
auth: authRouter,
signin: signInRouter,
signup: signUpRouter,
});
And when clicking on the register button:
async function onSumitRegisterValues(values: any) {
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
};
await fetch("http://localhost:3000/api/auth/signup", options)
.then((res) => res.json())
.then((data) => {
if (data?.ok) router.push("http://localhost:3000");
});
}
The values form contains nickname, email, password and cpassword to confirm password.
im getting a 500 on post
Server Error
TypeError: resolver is not a function
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Maybe its my lack of knowledge with trpc and next but ngl, its making me want to separate my backend into something different. But since im not rushing in building this project i really want to try to figure out what i shoud be doing better.
Why are you using fetch instead of using your useQuery method from trpc? The whole point of trpc is that you can skip fetch and will also have type safety.
https://trpc.io/docs/useQuery
I have a next.js app that has several API routes that I am hoping to protect from users who are not logged in. Using next-auth, I understand that I can add the following code to each API route to achieve this.
import { getSession } from 'next-auth/client'
export default async (req, res) => {
const session = await getSession({ req })
if (session) {
res.send({ content: 'This is protected content. You can access this content because you are signed in.' })
} else {
res.send({ error: 'You must be sign in to view the protected content on this page.' })
}
}
However, I was wondering if it is possible to use API middlewares, so I am not repeating the same code over and over again? I read through the Next.js API middlewares documentation (https://nextjs.org/docs/api-routes/api-middlewares) and did the following:
import Cors from 'cors';
import { getSession } from 'next-auth/react';
function initMiddleware(middleware) {
return (req, res) =>
new Promise((resolve, reject) => {
middleware(req, res, async (result) => {
const session = await getSession({ req });
if (!session) {
return reject(result);
}
return resolve(result);
});
});
}
const cors = initMiddleware(
Cors({
methods: ['GET', 'POST', 'OPTIONS'],
})
);
export default async function handler(req, res) {
await cors(req, res);
\* fetching from database *\
Although it works, the following error is returned when I tried to access the API route when unauthenticated, and it feels like I'm not doing it properly.
error - null
wait - compiling /_error (client and server)...
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
at DevServer.renderError (/Users/alextung/Desktop/Projects/askit/node_modules/next/dist/server/next-server.js:1677:17)
at DevServer.run (/Users/alextung/Desktop/Projects/askit/node_modules/next/dist/server/dev/next-dev-server.js:452:35)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async DevServer.handleRequest (/Users/alextung/Desktop/Projects/askit/node_modules/next/dist/server/next-server.js:325:20) {
code: 'ERR_HTTP_HEADERS_SENT'
}
error - Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Would really appreciate some help on this given that this is my first time working with middlewares. Thank you!
I have this function
exports.webhook = functions.https.onRequest((_req: any, res: { send: (arg0: string) => void; }) => {
// It sends notification to a user
});
I want this function to be executed at 3 PM daily. How do I schedule the function to run daily at this time ?
Edit 1
I have a function which sends notification, name of function is sendNotifications, how do I call this function from a URL and pass payload variables title and body
Sample working code:
exports.sendNotifications = functions.firestore
const payload = {
notification: {
title: no_of_followers2,
body: desc + ' Notification body',
icon: 'https://img.icons8.com/material/4ac144/256/user-male.png',
click_action: `https://google.com`,
}
};
... // some code
const subscriber = doc.data().token;
return admin.messaging().sendToDevice(subscriber, payload);
Edit 2
My function:
exports.sendNoti_cal_log = functions.https.onRequest((_req: any, res: { send: (arg0: string) => void; }) => {
const payload = {
notification: {
title: 'Notification Title',
body: 'Notification body',
icon: 'https://img.icons8.com/material/4ac144/256/user-male.png',
click_action: `https://google.com`,
}
};
const subscriber = "evGBnI_klVQYSBIPMqJbx8:APA91bEV5xOEbPwF4vBJ7mHrOskCTpTRJx0cQrZ_uxa-QH8HLomXdSYixwRIvcA2AuBRh4B_2DDaY8hvj-TsFJG_Hb6LJt9sgbPrWkI-eo0Xtx2ZKttbIuja4NqajofmjgnubraIOb4_";
return admin.messaging().sendToDevice(subscriber, payload)
});
Now when I am calling this function from URL, it works but I don't get any response, see screenshot:
Last thing I need is, how do i pass title from parameter and receive it in function.
Edit 3
My working Https function
//-------notification for calllogger
exports.sendNoti_cal_log = functions.https.onRequest((req: any, res: { status: (arg0: number) => { (): any; new(): any; send: { (arg0: { status: string; }): void; new(): any; }; }; }) => {
const payload = {
notification: {
title: 'Notification Title',
body: 'Notification body',
icon: 'https://img.icons8.com/material/4ac144/256/user-male.png',
click_action: `https://google.com`,
}
};
const subscriber = "evGBnI_klVQYSBIPMqJbx8:APA91bEV5xOEbPwF4vBJ7mHrOskCTpTRJx0cQrZ_uxa-QH8HLomXdSYixwRIvcA2AuBRh4B_2DDaY8hvj-TsFJG_Hb6LJt9sgbPrWkI-eo0Xtx2ZKttbIuja4NqajofmjgnubraIOb4_";
return admin.messaging().sendToDevice(subscriber, payload).then((messagingResponse: any) => {
res.status(200).send({status: "OK"})
})
});
Edit 4 (Passing Parameters)
I have passed parameter like this in my function:
exports.sendNoti_cal_log = functions.https.onRequest((req: any, res: { status: (arg0: number) => { (): any; new(): any; send: { (arg0: { status: string; }): void; new(): any; }; }; }) => {
const param1 = req.params.param1;
const payload = {
notification: {
title: 'Notification Title'+param1,
But when I am passing it from URL, it says undefined in the notification:
I am passing it like this -
https://us-central1-fir-crud-5b378.cloudfunctions.net/sendNoti_cal_log?param1=Hello
Update following your second update:
As you will read in the doc:
Always end an HTTP function with send(), redirect(), or end().
Otherwise, your function might continue to run and be forcibly
terminated by the system. See also Sync, Async and Promises.
So you need to do something like:
return admin.messaging().sendToDevice(subscriber, payload)
then(messagingResponse => {
res.status(200).send({status: "OK"});
})
For your question: "how do i pass title from parameter and receive it in function."
It is explained below in my first update ("In order to pass variables to an HTTP Cloud Function....").
Update following your comment and update:
The URL of your Cloud Function will be as follows:
https://us-central1-<project-id>.cloudfunctions.net/webhook
as detailed here in the doc.
In order to pass variables to an HTTP Cloud Function and get them in the CF, you should use the Request object which "gives you access to the properties of the HTTP request sent by the client" (see here in the doc).
For example, you can use the params property as follows to get query string parameters:
exports.webhook = functions.https.onRequest((_req: any, res: { send: (arg0: string) => void; }) => {
const param1 = _req.params.param1;
});
Initial answer:
Your Cloud Function is an HTTPS one which needs to be triggered through an HTTP request made from the "external world" to the Cloud Function platform.
So you need to schedule it from the caller/consumer end (for example a CRON service like https://cron-job.org/en/ which would issue an HTTP request to the corresponding Cloud Function URL, or one of your server, etc.).
If you want the Cloud Function platform to execute a specific Cloud Function at 3 PM daily, you should use a Scheduled Function as follows:
exports.webhook = functions.pubsub.schedule('0 15 * * *')
.timeZone('America/New_York') // Users can choose timezone - default is America/Los_Angeles
.onRun((context) => {
// It sends notification to a user
// return ...;
});
Note that a Scheduled Function is different from an HTTPS one (different trigger mechanism) but the business logic it implements can be the same (in your case, the code to send a notification).
I am new to redux, and working on an application. My sign-in and sign-up functionalities are working almost fine, except the fact that if some incorrect actions are being dispatched and not able to locate which part of the code is doing it. Below I am posting some code snippets.
Look at the second ghost LOGIN_FULFILLED Request, it should not occur as I don't have that user in the DB yet!
Screenshot for the actions and state transitions
Login action creators:
import request from 'axios';
import thunk from 'redux-thunk';
import store from '../store'
export function loginFunc(username, password) {
return {
type: 'LOGIN',
username,
password,
payload : request
.post("http://localhost:5000/users/authenticate",
{
username : username,
password: password
}
)
.then(function (response) {
console.log(response);
if (response.data.message === "user_found")
store.dispatch({type: 'LOGIN_FULFILLED', payload : response.data.results});
else
store.dispatch({type: 'LOGIN_REJECTED', payload : "user_not_found"});
})
.catch(function (error) {
console.log(error);
store.dispatch({type: 'LOGIN_REJECTED', payload : error});
})
}
}
Redux Thunk middleware allows you to write action creators that return a function instead of an action (as written in official guide).
You need to make few changes to make use of thunk. You need not import store to use getState and dispatch as they are arguments to the callback.
return function(dispatch, getState)
This dispatch, getState is same as store.dispatch and store.getState.
import request from 'axios';
export function loginFunc(username, password) {
return function(dispatch) {
request
.post("http://localhost:5000/users/authenticate", {
username: username,
password: password
})
.then(function(response) {
console.log(response);
if (response.data.message === "user_found")
dispatch({
type: 'LOGIN_FULFILLED',
payload: response.data.results
});
else
dispatch({
type: 'LOGIN_REJECTED',
payload: "user_not_found"
});
})
.catch(function(error) {
console.log(error);
dispatch({
type: 'LOGIN_REJECTED',
payload: error
});
})
}
}