javascript - Nodemailer Nextjs Typescript φόρμα δεν στέλνει email


Δουλεύω σε ένα προσωπικό έργο με Next.js Typescript και Nodemailer. Είναι η πρώτη φορά που χρησιμοποιώ Nodemailer. Έχω προβλήματα να το κάνω να δουλέψει. Ακολουθώ αυτό το σεμινάριο. Αυτός είναι ο κώδικάς μου παρακάτω όπως είναι στο σεμινάριο.

Nodemailer.ts: εδώ έπρεπε να αντικαταστήσω τον τύπο APIResponse με any επειδή ο κώδικας μου έδινε σφάλματα, και το VSCode μου πρότεινε να δημιουργήσω αυτή τη συνάρτηση στο κάτω μέρος του αρχείου. Επίσης, η θέση αυτού του αρχείου είναι /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 ',
          from: '[email ',
          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: Βρίσκεται στο /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
                </form>
            </div>
            
        </>
    )
}

export default Form;

Αυτή τη στιγμή έχω ένα σφάλμα 500 στο δίκτυο του προγράμματος περιήγησης, αλλά όταν άνοιξα τον σύνδεσμο, το σφάλμα αναφέρεται στο 404 του χειριστή Nodemailer.ts "Begone." Οποιαδήποτε βοήθεια θα εκτιμηθεί πολύ.

ΕΝΗΜΕΡΩΣΗ μετά την απάντηση του Άντον

Απάντηση #1

Πρώτον, πρέπει να εγκαταστήσετε το @types/nodemailer ως devDependencies.

Στο Form.tsx προσπαθείτε να στείλετε ένα κανονικό αντικείμενο (κλάση) αντί για JSON. Πρέπει να σειριοποιήσετε τα δεδομένα με JSON.stringify() και μπορείτε επίσης να προσθέσετε headers εδώ. Για απλοποίηση, μπορείτε να βάλετε απευθείας τις τιμές (states) στο JSON.stringify(). Και αφαιρέστε όλο το 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
});

Αφαίρεσα τη συνάρτηση formidablePromise() επειδή λαμβάνουμε και διαχειριζόμαστε όλα τα δεδομένα από τη συνάρτηση handler και το 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 ',
      from: '[email ',
      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;

Αυτό λειτουργεί με τις επιλογές Gmail του nodemailer μου.

Με export const config={api: {bodyParser: false}}

Χωρίς

Πρόβλημα διαπιστευτηρίων: Σφάλμα: Λείπουν διαπιστευτήρια για το "LOGIN"/"PLAIN"

Είχα LOGIN αντί για PLAIN επειδή έχω άλλες τιμές στο .env μου, πρέπει να ελέγξεις το .env σου. Αν είναι εντάξει, στο δεύτερο βήμα, δημιούργησε app passwords, γιατί δεν μπορείς να χρησιμοποιήσεις τον ίδιο κωδικό με αυτόν για το login.

Πώς να ορίσετε κωδικούς εφαρμογών Gmail, Hotmail

Επίσης, μπορείτε να βρείτε μια απάντηση σχετικά με Ελλείπουσες διαπιστευτήριες για το "PLAIN" εδώ