简体   繁体   中英

Sendgrid NodeJS recieved previous mail already sent

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
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM