Reputation: 346
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.
Upvotes: 2
Views: 2464
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.
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.
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.
export const config = {api: {bodyParser: false}}
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.
Also, you can find an answer about Missing credentials for "PLAIN" here
Upvotes: 1