Integrating SvelteKit SuperForms with Nodemailer in a SvelteKit App

Sveltekit Superforms Nodemailer

Enhancing User Experience in SvelteKit Apps with SvelteKit SuperForms and Nodemailer Integration

In modern web development, creating interactive and user-friendly forms is crucial for engaging and retaining users. SvelteKit, with its efficient and component-based architecture, offers an excellent foundation. When combined with SuperForm for form handling, Zod for schema validation, Svelte-Sonner for notifications, and Nodemailer for email integration, it becomes a powerhouse for creating sophisticated web applications. This blog post will guide you through integrating these technologies to enhance user experience in your SvelteKit applications.

Prerequisites

Install the necessary packages:

npm i nodemailer nodemailer-brevo-transport svelte-sonner sveltekit-superforms zod

Creating the Form Component

In your Svelte-Kit project, navigate to the src/routes directory and create a new file named +page.svelte. This file will contain our form component.

+page.svelte file

At the top of your +page.svelte file, import the required modules and set up the form with sveltekit-superforms and Zod for validation:

We will also import svelte-sonner for showing the toast message about user action

<script>
	import { Toaster, toast } from 'svelte-sonner';
	import { z } from 'zod';
	import { superForm } from 'sveltekit-superforms/client';
	import { opinionSchema } from '../../lib/formFunctions/FormSchema';

	export let data;

	const {
		form,
		enhance,
		message,
		capture,
		restore,
		errors,
		constraints,
		delayed,
		timeout,
		submitting
	} = superForm(data?.form, {
		resetForm: false,
		onSubmit() {
			toast('Your form is submitting...');
		}
	});
</script>

<Toaster richColors closeButton />

HTML Markup

Below the script tag, define the HTML structure for your form, utilizing Svelte's reactivity to handle form submission and display messages:

<form action="?/create" method="POST" use:enhance>
		<div class="flex flex-col space-y-3">
			<input
				class="input input-primary"
				placeholder="Type your name"
				name="name_title"
				type="text"
				bind:value={$form.name_title}
				{...$constraints.name_title}
			/>
			<div class="text-red-500">
				{#if $errors?.name_title}
					{$errors.name_title}
				{/if}
			</div>
			<input
				class="input input-primary"
				placeholder="Type your email"
				name="email"
				type="email"
				bind:value={$form.email}
				{...$constraints.email}
			/>
			<div class="text-red-500">
				{#if $errors?.email}
					{$errors.email}
				{/if}
			</div>
			<textarea
				class="textarea textarea-primary"
				placeholder="Type your message"
				name="detail"
				type="text"
				bind:value={$form.detail}
				{...$constraints.detail}
			/>
			<div class="text-red-500">
				{#if $errors?.detail}
					{$errors.detail}
				{/if}
			</div>
		</div>

		<button
			disabled={$delayed}
			class="btn {$delayed ? 'animate-pulse text-white ring' : 'btn-primary'}  mt-5 w-full"
		>
			{$delayed ? 'Sending Mail...' : 'Submit'}
		</button>
	</form>

Handling Form Submission on the Server

Now, let's set up the server-side handling of the form submission. Create a +page.server.js file in the same directory as your form component:

The Blueprint

import { superValidate, message } from 'sveltekit-superforms/server';
import { opinionSchema } from '../../lib/formFunctions/FormSchema';
import { mailPost } from '../../lib/mail/sendMail.js';

export const actions = {
	create: async ({ request }) => {
		// Form validation and email sending logic goes here
	}
};

The complete code

import { superValidate, message } from 'sveltekit-superforms/server';
import { opinionSchema } from '../../lib/formFunctions/FormSchema';
import { mailPost } from '../../lib/mail/sendMail.js';

export const load = async (event) => {
	const form = await superValidate(event, opinionSchema);

	return {
		form
	};
};

export const actions = {
	create: async ({ request }) => {
		const formData = await superValidate(request, opinionSchema);

		// await new Promise((resolve) => setTimeout(resolve, 5000));

		console.log('POST HERE', formData.data);

		// Extracting information from form
		const user_name = formData.data.name_title;
		const user_email = formData.data.email;
		const user_detail = formData.data.detail;
		console.log('user_name, user_email, user_detail: ', user_name, user_email, user_detail);


		if (!formData.valid) {
			// Again, return { form } and things will just work.
			return message(formData, {
				text: 'Something is wrong with your request, please correct and try again',
				status: 400
			});
		} else {
			const response = await mailPost({
				body: {
					user_name,
					user_email,
					user_detail
				}
			});

			// Check if response is successful
			if (response.status === 200) {
				return message(formData, {
					text: 'Your opinion has been received. Thank you!',
					status: 200
				});
			} else {
				console.log('response: ', response);
				return message(formData, { text: response.body, status: response.status });
			}
		}
	}
};

Setting Up Email with Nodemailer

Navigate to the lib/mail directory and create a sendMail.js file. Here, set up Nodemailer with your preferred transport (in this case, nodemailer-brevo-transport) and define the mailPost function to send an email with the form data:

The Blueprint

import nodemailer from 'nodemailer';
import brevoTransport from 'nodemailer-brevo-transport';

const transporter = nodemailer.createTransport(new brevoTransport({
	// Transport configuration
}));

export async function mailPost(request) {
	// Email sending logic goes here
}

Make sure to handle errors gracefully and send a response back to the client indicating the success or failure of the email operation.

The complete code

import nodemailer from 'nodemailer';
import brevoTransport from 'nodemailer-brevo-transport';

// IMPORT ENV VARIABLE
let brevoAPI = import.meta.env.VITE_BREVO_API;
// console.log(brevoAPI);

const transporter = nodemailer.createTransport(
	new brevoTransport({
		apiKey: brevoAPI
	})
);

export async function mailPost(request) {
	console.log('from sendmail js: ', request);
	const { user_name, user_email, user_detail } = request.body;
	const mailOptions = {
		from: user_email,
		to: '[email protected]',
		subject: `New message from  (${user_name})`,
		html: user_detail,
		text: user_detail
	};

	try {
		await transporter.sendMail(mailOptions);
		return {
			status: 200,
			body: 'Email sent successfully'
		};
	} catch (error) {
		console.error(error);
		return {
			status: 500,
			body: 'Failed to send email, Please try again'
		};
	}
}

Related posts