Skip to main content

Overview

The contact form uses EmailJS to send emails directly from the client-side without requiring a backend server. This guide walks through the complete setup process.

How It Works

The contact form in src/components/Contact/Contact.jsx uses three EmailJS credentials:
Contact.jsx
emailjs.send(
  import.meta.env.VITE_EMAILJS_SERVICE_ID,
  import.meta.env.VITE_EMAILJS_TEMPLATE_ID,
  {
    name: name,
    email: email,
    message: message,
  },
  import.meta.env.VITE_EMAILJS_PUBLIC_KEY
)
These credentials are stored in environment variables for security.

EmailJS Setup Guide

1

Create EmailJS Account

Visit EmailJS and sign up for a free account.The free tier includes:
  • 200 emails per month
  • Unlimited templates
  • All features
2

Add Email Service

  1. Go to the Email Services section in your dashboard
  2. Click Add New Service
  3. Choose your email provider (Gmail, Outlook, etc.)
  4. Follow the provider-specific instructions to connect your email
  5. Copy your Service ID (you’ll need this later)
For Gmail, you may need to create an App Password if you have 2FA enabled.
3

Create Email Template

  1. Navigate to Email Templates
  2. Click Create New Template
  3. Use this template structure:
Subject:
New Contact Form Submission from {{name}}
Body:
You have a new message from your portfolio:

Name: {{name}}
Email: {{email}}

Message:
{{message}}

---
Sent from your portfolio contact form
  1. Save the template and copy your Template ID
4

Get Public Key

  1. Go to AccountGeneral
  2. Find your Public Key in the API Keys section
  3. Copy this key
Never share your Private Key. Only use the Public Key in your frontend code.

Environment Variables Setup

EmailJS credentials are stored in a .env file for security.
1

Create .env File

In the root of your project (same level as package.json), create a file named .env:
touch .env
2

Add EmailJS Credentials

Open .env and add your credentials:
.env
VITE_EMAILJS_SERVICE_ID=service_xxxxxxx
VITE_EMAILJS_TEMPLATE_ID=template_xxxxxxx
VITE_EMAILJS_PUBLIC_KEY=your_public_key_here
Replace with your actual values from EmailJS.
Vite requires environment variables to be prefixed with VITE_ to be accessible in the frontend.
3

Add .env to .gitignore

Ensure .env is in your .gitignore file to keep credentials private:
.gitignore
# Environment variables
.env
.env.local
.env.production
4

Restart Development Server

For changes to take effect, restart your dev server:
npm run dev

Testing the Contact Form

1

Open Your Portfolio

Navigate to the contact section: http://localhost:5173/#contact
2

Fill Out the Form

Enter test data:
3

Submit and Verify

Click SEND MESSAGE. You should:
  1. See “Message sent!” alert
  2. Form fields clear automatically
  3. Receive an email at the address connected to your EmailJS service
If you don’t receive an email, check:
  • EmailJS dashboard for sent emails
  • Your spam folder
  • That your email service is properly connected
  • Browser console for any error messages

Contact Component Code

Here’s the full contact form implementation:
Contact.jsx
import styles from './Contact.module.css';
import emailjs from '@emailjs/browser';
import { useState } from 'react';

export default function Contact() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [message, setMessage] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();

    // Validation
    if (!name || !email || !message) {
      setError('');
      setTimeout(() => setError('Please fill in all fields.'), 10);
      return;
    }

    // Send email via EmailJS
    emailjs.send(
      import.meta.env.VITE_EMAILJS_SERVICE_ID,
      import.meta.env.VITE_EMAILJS_TEMPLATE_ID,
      {
        name: name,
        email: email,
        message: message,
      },
      import.meta.env.VITE_EMAILJS_PUBLIC_KEY
    )
      .then(() => {
        alert('Message sent!');
        setName('');
        setEmail('');
        setMessage('');
      })
      .catch((error) => {
        console.error(error);
        alert('Something went wrong.');
      });
  };

  return (
    <section id="contact" className={styles.section}>
      <form className={styles.form} onSubmit={handleSubmit}>
        <label htmlFor="name" className={styles.label}>NAME</label>
        <input 
          id="name" 
          type="text" 
          placeholder="Name" 
          className={styles.input} 
          value={name} 
          onChange={(e) => setName(e.target.value)} 
        />
        
        <label htmlFor="email" className={styles.label}>EMAIL</label>
        <input 
          id="email" 
          type="email" 
          placeholder="Email" 
          className={styles.input} 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
        />
        
        <label htmlFor="message" className={styles.label}>MESSAGE</label>
        <textarea 
          id="message" 
          className={`${styles.input} ${styles.textarea}`} 
          placeholder="Message" 
          value={message} 
          onChange={(e) => setMessage(e.target.value)}
        ></textarea>
        
        <div className={styles.buttonWrapper}>
          {error && <p className={styles.error}>{error}</p>}
          <button type="submit" className={styles.button}>SEND MESSAGE</button>
        </div>
      </form>
    </section>
  );
}

Customizing Form Fields

Adding New Fields

To add a new field (e.g., phone number):
1

Add State Variable

const [phone, setPhone] = useState('');
2

Add Input Field

<label htmlFor="phone" className={styles.label}>PHONE</label>
<input 
  id="phone" 
  type="tel" 
  placeholder="Phone" 
  className={styles.input} 
  value={phone} 
  onChange={(e) => setPhone(e.target.value)} 
/>
3

Include in EmailJS Payload

emailjs.send(
  import.meta.env.VITE_EMAILJS_SERVICE_ID,
  import.meta.env.VITE_EMAILJS_TEMPLATE_ID,
  {
    name: name,
    email: email,
    phone: phone,  // Add new field
    message: message,
  },
  import.meta.env.VITE_EMAILJS_PUBLIC_KEY
)
4

Update EmailJS Template

Add {{phone}} to your EmailJS email template to receive the phone number.

Removing Fields

To remove a field:
  1. Delete the state variable
  2. Remove the input JSX
  3. Remove it from the EmailJS send payload
  4. Update your EmailJS template

Error Handling

The contact form includes validation and error handling:

Form Validation

Contact.jsx
if (!name || !email || !message) {
  setError('');
  setTimeout(() => setError('Please fill in all fields.'), 10);
  return;
}
This checks that all fields are filled before sending.

EmailJS Error Handling

Contact.jsx
.then(() => {
  alert('Message sent!');
  // Clear form
  setName('');
  setEmail('');
  setMessage('');
})
.catch((error) => {
  console.error(error);
  alert('Something went wrong.');
});

Custom Error Messages

Enhance error handling with specific messages:
.catch((error) => {
  console.error('EmailJS error:', error);
  
  if (error.status === 400) {
    alert('Invalid email format. Please check your email address.');
  } else if (error.status === 403) {
    alert('Email service configuration error.');
  } else {
    alert('Failed to send message. Please try again later.');
  }
});

Email Validation

The form uses HTML5 email validation via type="email":
<input 
  id="email" 
  type="email"  // Built-in validation
  placeholder="Email" 
  className={styles.input} 
  value={email} 
  onChange={(e) => setEmail(e.target.value)} 
/>
For custom validation:
const validateEmail = (email) => {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
};

const handleSubmit = (e) => {
  e.preventDefault();
  
  if (!validateEmail(email)) {
    setError('Please enter a valid email address.');
    return;
  }
  
  // ... rest of submission logic
};

Changing Success/Error Messages

Customize user feedback:
// Success message
.then(() => {
  alert('Thank you! Your message has been sent successfully.');
  // Or use a toast notification library
})

// Error message
.catch((error) => {
  console.error(error);
  alert('Oops! Something went wrong. Please try again or email me directly.');
});
Consider using a toast notification library like react-hot-toast or react-toastify for better UX instead of browser alerts.

Production Deployment

When deploying to production:
1

Set Environment Variables on Host

Add your EmailJS credentials to your hosting platform’s environment variables:Vercel:
  • Go to Project Settings → Environment Variables
  • Add VITE_EMAILJS_SERVICE_ID, VITE_EMAILJS_TEMPLATE_ID, VITE_EMAILJS_PUBLIC_KEY
Netlify:
  • Go to Site Settings → Build & Deploy → Environment
  • Add the same variables
2

Rebuild Application

Trigger a new deployment after adding environment variables.
3

Test in Production

Submit a test message from your live site to verify the form works.
Environment variables are embedded at build time in Vite. You must rebuild your application after changing environment variables.

Troubleshooting

IssueSolution
”Message sent!” but no email receivedCheck EmailJS dashboard for logs. Verify email service connection.
undefined error for env variablesEnsure variables start with VITE_. Restart dev server.
Form submits but shows errorCheck browser console for detailed error. Verify all three credentials are correct.
”403 Forbidden” errorPublic Key is incorrect. Verify in EmailJS account settings.
Email goes to spamAdd a “Reply-To” header in EmailJS template using {{email}}.

Security Best Practices

Never Commit .env

Always keep .env in .gitignore to prevent credential exposure

Use Public Key Only

Never expose your EmailJS Private Key in frontend code

Rate Limiting

EmailJS has built-in rate limiting to prevent abuse

Input Sanitization

EmailJS automatically sanitizes inputs to prevent injection attacks

Alternative: Custom Backend

If you prefer not to use EmailJS, you can build a custom backend:
// Instead of emailjs.send(), use fetch to your API
fetch('/api/contact', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name, email, message })
})
  .then(res => res.json())
  .then(() => alert('Message sent!'))
  .catch(err => alert('Error sending message'));
This requires setting up a backend service (Node.js, Python, etc.) to handle email sending.

Next Steps

Deployment Guide

Deploy your portfolio to production

Content Customization

Personalize your portfolio content

Build docs developers (and LLMs) love