Integrating SvelteKit SuperForms with Nodemailer in a SvelteKit App

Sveltekit Superforms Nodemailer

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

sveltekit
nodemailer
svelte-sonner

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'
        };
    }
}