2

I am working on a personal project with Next.js Typescript and Nodemailer. It is my first time using Nodemailer. I am having issues making it work. I am following this tutorial. This is my code below as it is on the tutorial.

Nodemailer.ts: here I had to replace the type APIResponse for any due to the code gave me errors, and VSCode suggested me to create that function at the bottom of the file. Also, the location of this file is /pages/api/nodemailer.ts

import { NextApiHandler, NextApiRequest } from "next";

import nodemailer from "nodemailer";

type Fields = {
    name: string;
    message: string;
    email: string;
};


const transporter = nodemailer.createTransport({
    service: 'hotmail',
    auth: {
        user: process.env.NEXT_PUBLIC_EMAIL_ADDRESS,
        pass: process.env.NEXT_PUBLIC_PASSWORD,
    },
});

export const config = {
    api: {
      bodyParser: false,
    },
};

const handler: NextApiHandler<any> = async(req, res) => {
    if (req.method !== "POST") {
        return res.status(404).send({ error: "Begone." });
    }

    res.setHeader("Content-Type", "application/json")

    try {
        const { fields } = await formidablePromise(req, {});
        const { name, email, message } = fields;
    
        if (!name || !name.trim()) {
          throw new Error("Please provide a valid name.");
        }
    
        if (!email || !email.trim()) {
          throw new Error("Please provide a valid email address.");
        }
    
        if (!message || !message.trim()) {
          throw new Error("Please provide a valid email message.");
        }
    
        await transporter.sendMail({
          to: 'info@someemail.com.au',
          from: 'info@someemail.com.au',
          replyTo: email,
          subject: `Hello from ${name}`,
          text: message,
          html: `<p>${message.replace(/(?:\r\n|\r|\n)/g, "<br>")}</p>`,
        });
    
        res.status(200).json({});
      } catch (error) {
        res.status(500).json({ error: error });
    }
}

export default handler;

function formidablePromise(req: NextApiRequest, arg1: {}): { fields: any; } | PromiseLike<{ fields: any; }> {
    throw new Error("Function not implemented.");
}

Form.tsx: It is in /components/Form.tsx

import { FaFacebook, FaTwitter } from 'react-icons/fa';
import React, { ChangeEvent, FormEvent, useRef, useState } from 'react';

import styles from './Form.module.css';

export interface FormProps {
    result: boolean
    isChecked: boolean
    callTime: {time: string, isChecked: boolean}[]

    loading: boolean
}

const Form: React.FC<FormProps> = () => {    

    const [loading, setLoading] = useState<boolean>(false)
    const [name, setName] = useState<string>("");
    const [email, setEmail] = useState<string>("");
    const [mobile, setMobile] = useState<string | number | any>("");
    const [message, setMessage] = useState<string>("");

    console.log('NAme:', name, ', email', email, ', mobile', mobile, ', message', message);

    async function sendEmail(event: FormEvent) {
      event.preventDefault();
    
      setLoading(true);
    
      try {
        const formData = new FormData();
    
        if (!name.trim()) {
          throw new Error("Please provide a valid name.");
        }
    
        if (!email.trim()) {
          throw new Error("Please provide a valid email address.");
        }

        if (!mobile.trim()) {
          throw new Error("Please provide a valid mobile number.");
        }
    
        if (!message.trim()) {
          throw new Error("Please provide a valid message.");
        }
    
        formData.append("name", name);
        formData.append("email", email);
        formData.append("mobile", mobile);
        formData.append("message", message);

        console.log('form data', formData);
    
        const response = await fetch("/api/nodemailer", {
          method: "POST",
          body: formData,
        });
    
        const responseData = await response.json();
        console.log('form responseData', responseData);
    
        if (responseData.error) {
          throw new Error(responseData.error);
        }
    
        console.log("Thanks, we will be in touch soon!");
    
        setName("");
        setEmail("");
        setMobile("");
        setMessage("");
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }

      
      
    }
    
      console.log('send email', sendEmail);
    

    return (
        <>
            <div className={styles.wrapper}>
                <form 
                onSubmit={sendEmail} 
                className={styles.formContainer}>
                    <h3>Get <span>in</span> touch</h3>
                    <label>Full Name<span className={styles.required}>*</span></label>
                    <input 
                      type="text" 
                      name="name" 
                      required
                      value={name}
                      onChange={({ target }: ChangeEvent) => setName(( target as HTMLInputElement ).value)}
                    />
                    <div className={styles.twoInputs}>
                        <div className={styles.innerInputs}>
                            <label>Email</label>
                            <input 
                              type="email" 
                              name="email"
                              pattern=".+@.+\..+"
                              value={email}
                              onChange={({ target }: ChangeEvent) => setEmail(( target as HTMLInputElement ).value)}
                            />
                        </div>
                        <div className={styles.innerInputs}>
                            <label>Mobile<span className={styles.required}>*</span></label>
                            <input 
                              type="tel" 
                              name="tel" 
                              required
                              value={mobile}
                              onChange={({ target }: ChangeEvent) => setMobile((target as HTMLInputElement ).value)} />
                        </div>
                    </div>

                    <label>Message<span className={styles.required}>*</span></label>
                    <textarea 
                      name="message" 
                      rows={6} 
                      required 
                      value={message}
                      maxLength={1000}
                      onChange={({ target }: ChangeEvent) => setMessage(( target as HTMLInputElement ).value)}
                    />
                    <input type="submit" value="Send" />
                    <small><span className={styles.required}>*</span>Required</small>
                </form>
            </div>
            
        </>
    )
}

export default Form;

At the moment I having an error 500 on the browser network, but when I opened the link the error references to the 404 of the Nodemailer.ts handler "Begone." Any help would be much appreacciate it.

UPDATE after Anton answer enter image description here

enter image description here

Joaquin Palacios
  • 287
  • 8
  • 33

1 Answers1

1

First, you need install @types/nodemailer as a devDependencies.

In the Form.tsx you are trying to send a regular object (class) instead of JSON. You must serialize the data with JSON.stringify() and you can also add a headers here. To simplify, you can directly put the values (states) to JSON.stringify(). And remove all formData.

Form.tsx

const response = await fetch('/api/nodemailer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name, email, mobile, message }), // Serialize JSON
});

I removed the formidablePromise() function because we recive and handling all data from the handler function and req.body.

nodemailer.ts

import { NextApiResponse, NextApiRequest } from 'next';
import nodemailer from 'nodemailer';

type Fields = {
  name: string;
  message: string;
  mobile: string;
  email: string;
};

type Response = {
  error?: string;
  status?: string;
  message?: string;
};

const transporter = nodemailer.createTransport({
  service: 'hotmail',
  auth: {
    user: process.env.NEXT_PUBLIC_EMAIL_ADDRESS,
    pass: process.env.NEXT_PUBLIC_PASSWORD,
  },
});

//export const config = {
//  api: {
//    bodyParser: false,
//  },
//};

const handler = async (req: NextApiRequest, res: NextApiResponse<Response>) => {
  const { name, email, message } = req.body as Fields;

  if (req.method !== 'POST') {
    return res.status(404).send({ status: 'fail', error: 'Begone.' });
  }

  try {
    if (!name || !name.trim()) {
      throw new Error('Please provide a valid name.');
    }

    if (!email || !email.trim()) {
      throw new Error('Please provide a valid email address.');
    }

    if (!message || !message.trim()) {
      throw new Error('Please provide a valid email message.');
    }

    await transporter.sendMail({
      to: 'info@someemail.com.au',
      from: 'info@someemail.com.au',
      replyTo: email,
      subject: `Hello from ${name}`,
      text: message,
      html: `<p>${message.replace(/(?:\r\n|\r|\n)/g, '<br>')}</p>`,
    });

    res.status(200).send({ status: 'done', message: 'message has been sent' });
  } catch (error) {
    res.status(500).send({ status: 'fail', error: `${error}` });
  }
};

export default handler;

This works with my nodemailer Gmail options.

With export const config = {api: {bodyParser: false}}

enter image description here

Without

enter image description here

Credentials issue: Error: Missing credentials for "LOGIN" / "PLAIN"

enter image description here

I had LOGIN instead of PLAIN because I have other values in my .env, you need to check your .env. If it is fine, in the second step, create a app passwords, because you can't to use the same password as for login in.

How to set app passwords Gmail, Hotmail

Also, you can find an answer about Missing credentials for "PLAIN" here

Anton
  • 8,058
  • 1
  • 9
  • 27
  • Hi @Anton thanks for your answer. Unfortunately, it doesn't work. I still having the same error as before. On the terminal the error says that: error - (api)\pages\api\nodemailer.ts (34:10) @ handler TypeError: Cannot destructure property 'name' of 'req.body' as it is undefined. 32 | 33 | const handler = async(req: NextApiRequest, res: NextApiResponse) => { > 34 | const { name, email, message } = req.body as Fields; | ^ 35 | 36 | if (req.method !== "POST") { 37 | return res.status(404).send({ status: 'fail', error: "Begone." }); – Joaquin Palacios Jul 11 '22 at 10:53
  • and on the browser the error says that: index.tsx?9a58:76 POST http://localhost:3000/api/nodemailer 500 (Internal Server Error) – Joaquin Palacios Jul 11 '22 at 10:54
  • I tried also with gmail and it doesn't work either. – Joaquin Palacios Jul 11 '22 at 10:59
  • 1
    Remove `export const config = {api: {bodyParser: false}}` from **nodemailer.ts** – Anton Jul 11 '22 at 11:12
  • Thank you @anton again. Now, the error on the console disapear. But, the form still not working. It still having the error 505 on the browser. I'll update the post with screenshots for you to see. – Joaquin Palacios Jul 11 '22 at 21:12
  • It looks like **.env** values are incorrect or you don't have permission to send via email. I'm not using hotmail, but for gmail you need to create (generate) you own password for third party libraries then replace your password. – Anton Jul 12 '22 at 07:10
  • Thanks for all your help @anton I need to set up that now nad hopefully works. – Joaquin Palacios Jul 12 '22 at 09:10