How to Send Emails in a Lovable React App Using EmailJS

On May 20, 2025
13min read
Artem Litvinenko Software Engineer
Ivan Djuric, an author at Mailtrap
Ivan Djuric Technical Content Writer @Mailtrap

In this article, I’ll show you how to create a React app by prompting Lovable AI. Then we’ll integrate EmailJS with Mailtrap to send emails.

I’ll cover the following:

Disclaimer: The workflow in this article has been prepared and tested by a developer before publication.

Ready to deliver your emails?
Try Mailtrap for Free

Setting up EmailJS

EmailJS is a client-side or front-end solution for sending emails in JavaScript, which can also be used to send emails via TypeScript.

Here’s how you can set it up:

1. Register and set up EmailJS

As you’ve signed up, ensure the ‘Notifications’ section in the ‘Account’ tab has an email with the same domain as your Mailtrap account.

2. Connect your email-sending service

Then, navigate to Email Services and select a Mailtrap email provider:

Next, complete the setup wizard with the following details:

Notes:

  • Service ID – This field will have a pre-generated ID, although you can come up with your own.
  • Username and Password – You need to copy these two from the ‘Integrations’ page within your Mailtrap account.
  • Email Feature – Here, you need to select the ‘Sending’ feature from the dropdown menu. If you experience issues with verifying configuration when trying to create a Mailtrap service in EmailJS, uncheck “Send test email to verify configuration”.

3. Create an email template

Once you’re finished with the previous step, go to Email Templates and create a new template. 

Add placeholders like {{name}}, {{email}}, and {{{html_message}}} in the email body. 

Tip: To format the HTML code within the placeholders, use triple curved brackets instead of double. When emails are sent, these get replaced with the sender’s name, email, and message HTML content accordingly. 

Save the template and note its Template ID.

If you followed the steps correctly, your template should look similar to the one in the screenshot below. If not, go ahead, click Edit Content, choose Code Editor, and modify the template as you want.

Important Note: In the right sidebar, set your From Email to the address associated with the Mailtrap Account’s email domain. Otherwise, Mailtrap will block SMTP requests if they aren’t sent from the same domain.

Lastly, within EmailJS, go to Account > API Keys and take note of your Public Key. As a result, you should now have 2 IDs and one key: Public Key, Service ID, and Template ID

Create a React contact form

To generate a React contact form using Lovable, we can use a simple prompt like this one:

Create a react form containing the following fields: "name", "email", "html_message". 
"name" input is a string input"email" input is an html input"html_message" input is textarea, supporting any string including html code
This should be a simple example form that should send emails (do not implement the actual email sending logic yet, just a placeholder for it)

Copy/paste it in Lovable’s prompt field on the main page, like so:

Pro Tip: You can upload pictures, which Lovable can use as a context for the prompt to produce more desirable, expected, or stable results.

After executing the prompt by clicking the black button at the bottom right corner, Lovable will create a basic React app containing the desired styled form. 

Anyway, I’ll switch the tab and select Code (<>). Since we didn’t provide any tool-specific requirements, Lovable decided to use popular tools, such as TypeScript and Tailwind, to simplify things. 

We’ll follow this default example further in the guide, but if you have any specific tool requirements, include them in your prompt.

And here’s the form code snippet generated by Lovable:

import React from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/use-toast";
import { sendEmail } from "@/services/emailService";

// Form schema with validation
const formSchema = z.object({
  name: z.string().min(2, { message: "Name must be at least 2 characters." }),
  email: z.string().email({ message: "Please enter a valid email address." }),
  html_message: z.string().min(10, { message: "Message must be at least 10 characters." }),
});

const EmailForm = () => {
  // Define form using react-hook-form with zod validation
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: "",
      email: "",
      html_message: "",
    },
  });

  // Handle form submission
  const onSubmit = async (values: z.infer<typeof formSchema>) => {
    try {
      // This is just a placeholder - actual email sending logic will be implemented later
      await sendEmail(values);
      
      toast({
        title: "Form submitted!",
        description: "Your message has been received.",
      });
      
      form.reset();
    } catch (error) {
      toast({
        title: "Something went wrong.",
        description: "Your message could not be sent. Please try again.",
        variant: "destructive",
      });
    }
  };

  return (
    <div className="w-full max-w-md mx-auto">
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
          <FormField
            control={form.control}
            name="name"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Name</FormLabel>
                <FormControl>
                  <Input placeholder="John Doe" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="email"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Email</FormLabel>
                <FormControl>
                  <Input type="email" placeholder="john.doe@example.com" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="html_message"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Message (HTML supported)</FormLabel>
                <FormControl>
                  <Textarea 
                    placeholder="Enter your message here, HTML tags are supported..." 
                    className="min-h-[120px]" 
                    {...field} 
                  />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <Button type="submit" className="w-full">Send Message</Button>
        </form>
      </Form>
    </div>
  );
};

export default EmailForm;

Note: To change code within the Lovable code editor, you have to synchronize with Github.

Send email in React using EmailJS

Now, to modify the form to connect our EmailJS email-sending logic on form submission, you can use a simple prompt in the sidebar prompt field, like, for example, this one:

Add EmailJS integration to the current form

After executing the prompt, Lovable will create a new file named src/services/emailService.ts to store the following email-sending logic:

import emailjs from '@emailjs/browser';

interface EmailData {
  name: string;
  email: string;
  html_message: string;
}

export const sendEmail = async (data: EmailData): Promise<void> => {
  try {
    // Initialize EmailJS with your public key (this is safe to expose in client-side code)
    emailjs.init("YOUR_PUBLIC_KEY");
    
    // Send the email using EmailJS
    const result = await emailjs.send(
      "YOUR_SERVICE_ID",       // EmailJS service ID
      "YOUR_TEMPLATE_ID",      // EmailJS template ID
      data
    );
    
    console.log("Email sent successfully:", result);
    return Promise.resolve();
  } catch (error) {
    console.error("Failed to send email:", error);
    return Promise.reject(error);
  }
};

Code breakdown:

  • EmailJS is initialized using emailjs.init with the Public Key mentioned before.
  • The code sends an email using emailjs.send, passing service ID, template ID and data for the template accordingly.
  • The result is logged in the console.

Then, lovable will automatically update the EmailForm.ts file to pass the correct field names (e.g., {{name}}, {{email}}, etc.) to EmailJS:

import React from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/use-toast";
import { sendEmail } from "@/services/emailService";

// Form schema with validation
const formSchema = z.object({
  name: z.string().min(2, { message: "Name must be at least 2 characters." }),
  email: z.string().email({ message: "Please enter a valid email address." }),
  html_message: z.string().min(10, { message: "Message must be at least 10 characters." }),
});

const EmailForm = () => {
  // Define form using react-hook-form with zod validation
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: "",
      email: "",
      html_message: "",
    },
  });

  // Handle form submission
  const onSubmit = async (values: z.infer<typeof formSchema>) => {
    try {
      // Send email with EmailJS
      await sendEmail({
        name: values.name,
        email: values.email,
        html_message: values.html_message,
      });
      
      toast({
        title: "Email sent!",
        description: "Your message has been sent successfully.",
      });
      
      form.reset();
    } catch (error) {
      toast({
        title: "Something went wrong.",
        description: "Your message could not be sent. Please try again.",
        variant: "destructive",
      });
    }
  };

  return (
    <div className="w-full max-w-md mx-auto">
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
          <FormField
            control={form.control}
            name="name"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Name</FormLabel>
                <FormControl>
                  <Input placeholder="John Doe" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="email"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Email</FormLabel>
                <FormControl>
                  <Input type="email" placeholder="john.doe@example.com" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="html_message"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Message (HTML supported)</FormLabel>
                <FormControl>
                  <Textarea 
                    placeholder="Enter your message here, HTML tags are supported..." 
                    className="min-h-[120px]" 
                    {...field} 
                  />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <Button type="submit" className="w-full">Send Message</Button>
        </form>
      </Form>
    </div>
  );
};

export default EmailForm;

Key changes:

  • Lovable replaced the previous submit method with the new EmailJS-based method from the previous snippet.

Notes on sending plain-text messages

Since we use {{{message}}} variable, the messages we enter are automatically formatted as HTML when we send them, and the variable can accept both HTML and simple messages. 

If you want your emails to behave strictly as a ‘plain-text’, simply replace the triple brackets with double brackets {{message}} in the EmailJS dashboard, just like so:

Send HTML email

If you try to run the current solution, you’ll see an error. This happens because Lovable doesn’t know anything about our credentials, and you need to manually replace the example keys with the keys from the EmailJS account. 

Also, in the sidebar, you’ll see a message from Lovable highlighting this issue.

To fix it, manually update the code in the Code tab to include the keys actual keys from the EmailJS dashboard:

emailjs.init('YOUR_PUBLIC_KEY');

// Send the email using EmailJS
const result = await emailjs.send(
  'YOUR_SERVICE_ID', // EmailJS service ID
  'YOUR_TEMPLATE_ID', // EmailJS template ID
  {
    from_name: data.name,
    from_email: data.email,
    message_html: data.html_message,
  },
);

Then, if you try to send an HTML message, you should see a block with a success message, like the one below. ⬇️

Send an email with an attachment

Here, I need to stress that using attachments is only available with a paid EmailJS plan, a ‘Personal Plan’ at least. Using the free plan, you won’t be able to proceed with the configuration. 

With that out of the way, here’s what you need to do to send emails with attachments: 

  • Open the Attachments tab in your selected template.
  • Click Add Attachment.
  • Select Variable Attachment.
  • Enter the filename, content type (default), and parameter name that you’re going to use later in your HTML code. 
  • Click Apply and Save the template.

Now is the time for a new prompt since we need to add a new input in the React form for file selection:

Add an ability to attach a file. The element should have "my_file" name

Important Note: Make sure the name in the prompt matches the attachment’s name in the EmailJS dashboard. 

After running the prompt, Lovable will include the attachments field in the src/services/emailService.ts script. Check it out:

import emailjs from '@emailjs/browser';

interface EmailData {
  name: string;
  email: string;
  html_message: string;
  my_file: string;
}

export const sendEmail = async (data: EmailData): Promise<void> => {
  try {
    // Initialize EmailJS with your public key (this is safe to expose in client-side code)
    emailjs.init("PUBLIC_KEY");
    
    // Prepare the template params
    const templateParams = {
      name: data.name,
      email: data.email,
      html_message: data.html_message,
      my_file: data.my_file
    };
    
    // Send the email using EmailJS
    const result = await emailjs.send(
      "SERVICE_ID",       // EmailJS service ID
      "TEMPLATE_ID",      // EmailJS template ID
      templateParams
    );
    
    console.log("Email sent successfully:", result);
    return Promise.resolve();
  } catch (error) {
    console.error("Failed to send email:", error);
    return Promise.reject(error);
  }
};

Key changes:

  • Added the ‘my_file’ param to handle the file attachment.

Lovable will also update our EmailForm.ts file:

import React, { useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/use-toast";
import { sendEmail } from "@/services/emailService";
import { Paperclip } from "lucide-react";

// Form schema with validation
const formSchema = z.object({
  name: z.string().min(2, { message: "Name must be at least 2 characters." }),
  email: z.string().email({ message: "Please enter a valid email address." }),
  html_message: z.string().min(10, { message: "Message must be at least 10 characters." }),
  my_file: z.instanceof(FileList).optional().refine(
    (files) => !files || files.length === 0 || files[0].size <= 5000000, 
    { message: "File size should be less than 5MB." }
  ),
});

const EmailForm = () => {
  const [isUploading, setIsUploading] = useState(false);
  const [fileName, setFileName] = useState("");

  // Define form using react-hook-form with zod validation
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: "",
      email: "",
      html_message: "",
    },
  });

  // Handle file change
  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files;
    if (files && files.length > 0) {
      setFileName(files[0].name);
    } else {
      setFileName("");
    }
  };

  // Handle form submission
  const onSubmit = async (values: z.infer<typeof formSchema>) => {
    try {
      setIsUploading(true);
      
      // Handle the file
      let fileData = null;
      const fileList = values.my_file as FileList | undefined;
      
      if (fileList && fileList.length > 0) {
        const file = fileList[0];
        const reader = new FileReader();
        
        // Convert file to base64
        fileData = await new Promise<string>((resolve, reject) => {
          reader.onload = (event) => {
            if (event.target && typeof event.target.result === 'string') {
              resolve(event.target.result);
            } else {
              reject(new Error('Failed to read file'));
            }
          };
          reader.onerror = () => reject(reader.error);
          reader.readAsDataURL(file);
        });
      }
      
      // Send email with EmailJS
      await sendEmail({
        name: values.name,
        email: values.email,
        html_message: values.html_message,
        my_file: fileData,
      });
      
      toast({
        title: "Email sent!",
        description: "Your message has been sent successfully.",
      });
      
      form.reset();
      setFileName("");
    } catch (error) {
      toast({
        title: "Something went wrong.",
        description: "Your message could not be sent. Please try again.",
        variant: "destructive",
      });
    } finally {
      setIsUploading(false);
    }
  };

  return (
    <div className="w-full max-w-md mx-auto">
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
          <FormField
            control={form.control}
            name="name"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Name</FormLabel>
                <FormControl>
                  <Input placeholder="John Doe" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="email"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Email</FormLabel>
                <FormControl>
                  <Input type="email" placeholder="john.doe@example.com" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="html_message"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Message (HTML supported)</FormLabel>
                <FormControl>
                  <Textarea 
                    placeholder="Enter your message here, HTML tags are supported..." 
                    className="min-h-[120px]" 
                    {...field} 
                  />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="my_file"
            render={({ field: { ref, onChange, value, ...rest } }) => (
              <FormItem>
                <FormLabel>Attachment</FormLabel>
                <FormControl>
                  <div className="flex items-center gap-2">
                    <Button 
                      type="button" 
                      variant="outline" 
                      className="relative flex items-center w-full"
                      onClick={() => document.getElementById('file-upload')?.click()}
                    >
                      <Paperclip className="mr-2 h-4 w-4" />
                      {fileName || "Choose file"}
                      
                      <input
                        id="file-upload"
                        type="file"
                        className="sr-only"
                        ref={ref}
                        onChange={(e) => {
                          onChange(e.target.files);
                          handleFileChange(e);
                        }}
                        {...rest}
                      />
                    </Button>
                    
                    {fileName && (
                      <Button 
                        type="button" 
                        variant="ghost" 
                        size="sm"
                        className="px-2"
                        onClick={() => {
                          const fileInput = document.getElementById('file-upload') as HTMLInputElement;
                          if (fileInput) fileInput.value = '';
                          onChange(undefined);
                          setFileName("");
                        }}
                      >
                        Clear
                      </Button>
                    )}
                  </div>
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <Button 
            type="submit" 
            className="w-full" 
            disabled={isUploading}
          >
            {isUploading ? "Sending..." : "Send Message"}
          </Button>
        </form>
      </Form>
    </div>
  );
};

export default EmailForm;

Key changes:

  • Updated the zod schema.
  • Added file handling and input to select files.

Now, you can fill out the form, add an attachment, and send the email:

Send email to multiple recipients

To send emails to multiple recipients,add a {{carbon_copy}} variable in the Bcc field: 

P.S. We have a dedicated article on the topic of using and testing CC and BCC, check it out. 👀

For this demo, we’ll use a template value so that we can dynamically pass the recipients by mentioning them in the form. But you can also use static emails for the field to send to some specific recipients.

Again, we’re starting with a new prompt:

Add an ability to mention multiple recipients using commas. The element should have "carbon_copy" name and be a simple input

After executing the prompt, Lovable will update our EmailForm.tsx with the following code:

import React, { useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/use-toast";
import { sendEmail } from "@/services/emailService";
import { Paperclip } from "lucide-react";

// Form schema with validation
const formSchema = z.object({
  name: z.string().min(2, { message: "Name must be at least 2 characters." }),
  email: z.string().email({ message: "Please enter a valid email address." }),
  carbon_copy: z.string().optional(),
  html_message: z.string().min(10, { message: "Message must be at least 10 characters." }),
  my_file: z.instanceof(FileList).optional().refine(
    (files) => !files || files.length === 0 || files[0].size <= 5000000, 
    { message: "File size should be less than 5MB." }
  ),
});

const EmailForm = () => {
  const [isUploading, setIsUploading] = useState(false);
  const [fileName, setFileName] = useState("");

  // Define form using react-hook-form with zod validation
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: "",
      email: "",
      carbon_copy: "",
      html_message: "",
    },
  });

  // Handle file change
  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files;
    if (files && files.length > 0) {
      setFileName(files[0].name);
    } else {
      setFileName("");
    }
  };

  // Handle form submission
  const onSubmit = async (values: z.infer<typeof formSchema>) => {
    try {
      setIsUploading(true);
      
      // Handle the file
      let fileData = null;
      const fileList = values.my_file as FileList | undefined;
      
      if (fileList && fileList.length > 0) {
        const file = fileList[0];
        const reader = new FileReader();
        
        // Convert file to base64
        fileData = await new Promise<string>((resolve, reject) => {
          reader.onload = (event) => {
            if (event.target && typeof event.target.result === 'string') {
              resolve(event.target.result);
            } else {
              reject(new Error('Failed to read file'));
            }
          };
          reader.onerror = () => reject(reader.error);
          reader.readAsDataURL(file);
        });
      }
      
      // Send email with EmailJS
      await sendEmail({
        name: values.name,
        email: values.email,
        carbon_copy: values.carbon_copy || "",
        html_message: values.html_message,
        my_file: fileData || "",
        file_name: fileName || "",
      });
      
      toast({
        title: "Email sent!",
        description: "Your message has been sent successfully.",
      });
      
      form.reset();
      setFileName("");
    } catch (error) {
      toast({
        title: "Something went wrong.",
        description: "Your message could not be sent. Please try again.",
        variant: "destructive",
      });
    } finally {
      setIsUploading(false);
    }
  };

  return (
    <div className="w-full max-w-md mx-auto">
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
          <FormField
            control={form.control}
            name="name"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Name</FormLabel>
                <FormControl>
                  <Input placeholder="John Doe" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="email"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Email</FormLabel>
                <FormControl>
                  <Input type="email" placeholder="john.doe@example.com" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="carbon_copy"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Carbon Copy (CC)</FormLabel>
                <FormControl>
                  <Input 
                    type="text" 
                    placeholder="person1@example.com, person2@example.com" 
                    {...field} 
                  />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="html_message"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Message (HTML supported)</FormLabel>
                <FormControl>
                  <Textarea 
                    placeholder="Enter your message here, HTML tags are supported..." 
                    className="min-h-[120px]" 
                    {...field} 
                  />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <FormField
            control={form.control}
            name="my_file"
            render={({ field: { ref, onChange, value, ...rest } }) => (
              <FormItem>
                <FormLabel>Attachment</FormLabel>
                <FormControl>
                  <div className="flex items-center gap-2">
                    <Button 
                      type="button" 
                      variant="outline" 
                      className="relative flex items-center w-full"
                      onClick={() => document.getElementById('file-upload')?.click()}
                    >
                      <Paperclip className="mr-2 h-4 w-4" />
                      {fileName || "Choose file"}
                      
                      <input
                        id="file-upload"
                        type="file"
                        className="sr-only"
                        ref={ref}
                        onChange={(e) => {
                          onChange(e.target.files);
                          handleFileChange(e);
                        }}
                        {...rest}
                      />
                    </Button>
                    
                    {fileName && (
                      <Button 
                        type="button" 
                        variant="ghost" 
                        size="sm"
                        className="px-2"
                        onClick={() => {
                          const fileInput = document.getElementById('file-upload') as HTMLInputElement;
                          if (fileInput) fileInput.value = '';
                          onChange(undefined);
                          setFileName("");
                        }}
                      >
                        Clear
                      </Button>
                    )}
                  </div>
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          
          <Button 
            type="submit" 
            className="w-full" 
            disabled={isUploading}
          >
            {isUploading ? "Sending..." : "Send Message"}
          </Button>
        </form>
      </Form>
    </div>
  );
};

export default EmailForm;

Key changes:

  • Added the ‘carbon_copy’ field, where multiple email recipients can be specified.

Reminder: Please verify that the ‘carbon_copy’ field name from the EmailJS dashboard matches the field name in your code.

Correspondingly, you’ll notice the CC recipients field in the form:

Lastly, run the code, fill out the form, and you should see the email delivered to multiple recipients.

Supabase Integration

As you may have noticed, Supabase can also be integrated in Lovable. This provides you with more advanced and safe server-side possibilities for email sending (e.g. Sending verification emails on sign up, setting up various notification emails, etc.)

To connect your Supabase account to Lovable, click Manage Supabase icon.

In the opened window, click Connect Supabase. It will ask you to authorize Lovable in Supabase, click Authorize Lovable.

Just like that, your Supabase account has been successfully connected to Lovable. Also, if you’re considering using Supabase for your Lovable project, we recommend you refer to our dedicated article.

Further reading:

We’ve also covered topics on sending emails with other latest tech, such as:

Article by Artem Litvinenko Software Engineer

Software engineer with over 4 years of experience.

Ivan Djuric, an author at Mailtrap
Article by Ivan Djuric Technical Content Writer @Mailtrap

I’m a Technical Content Writer with 5 years of background covering email-related topics in tight collaboration with software engineers and email marketers. I just love to research and share actionable insights with you about email sending, testing, deliverability improvements, and more. Happy to be your guide in the world of emails!