Joaquin Palacios
Joaquin Palacios

Reputation: 346

Nodemailer Nextjs Typescript form not sending emails

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: '[email protected]',
          from: '[email protected]',
          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

Upvotes: 2

Views: 2464

Answers (1)

Anton
Anton

Reputation: 8538

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: '[email protected]',
      from: '[email protected]',
      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

Upvotes: 1

Related Questions