Endpoint
Accepts contact form submissions from customers and sends them via email to the shop owner. Supports optional file attachments up to 10MB.
Authentication
This endpoint does not require authentication. It’s publicly accessible to allow visitors to contact the shop.
This endpoint accepts multipart/form-data to support file uploads.
Request Parameters
Sender’s email address. Must be a valid email format.
Optional file attachment (max 10MB)
Response
Indicates whether the message was sent successfully
Success confirmation message in French
Error Codes
Status Code Error Message Description 400Tous les champs sont requis (nom, email, sujet, message)One or more required fields are missing 400Format d'email invalideEmail format validation failed 500Erreur lors de l'envoi du messageServer error sending email
Validation Rules
Emails must match the regex pattern:
/ ^ [ ^ \s@ ] + @ [ ^ \s@ ] + \. [ ^ \s@ ] + $ /
Valid examples:
customer@example.com
jean.dupont@mail.fr
contact+inquiry@domain.co.uk
Invalid examples:
invalid.email (missing @)
@example.com (missing local part)
user@ (missing domain)
File Upload Limits
Max file size: 10MB (10,485,760 bytes)
Storage: Files are stored in memory (not saved to disk)
Allowed types: All file types accepted
Files exceeding 10MB will be rejected before reaching the endpoint, resulting in a 413 Payload Too Large error.
Email Delivery
Messages are sent to sabbelshandmade@gmail.com using the Resend email service.
Email Content
The email includes:
Sender’s name and email (with reply-to set to sender’s email)
Subject line
Message content (preserves line breaks)
Timestamp
File attachment (if provided)
Email Configuration
{
from : 'Sabbels Handmade Contact <onboarding@resend.dev>' ,
to : 'sabbelshandmade@gmail.com' ,
replyTo : email , // Sender's email
subject : `📬 ${ subject } - de ${ name } ` ,
html : emailHtml ,
attachments : [ /* if file attached */ ]
}
Test Mode
If RESEND_API_KEY is not configured:
The endpoint still accepts requests
Messages are logged to console instead of sent
Returns success response: Message reçu (mode test)
Example Requests
cURL (without attachment)
cURL (with attachment)
JavaScript (Fetch API)
Python (requests)
curl -X POST https://your-api-domain.com/api/contact \
-F "name=Jean Dupont" \
-F "email=jean.dupont@example.com" \
-F "subject=Question about custom orders" \
-F "message=Bonjour, I would like to know if you accept custom crochet orders for baby blankets. Thank you!"
Example Responses
Success Response
{
"success" : true ,
"message" : "Message envoyé avec succès"
}
Success Response (Test Mode)
{
"success" : true ,
"message" : "Message reçu (mode test)"
}
Error Response - Missing Fields
{
"error" : "Tous les champs sont requis (nom, email, sujet, message)"
}
Error Response - Invalid Email
{
"error" : "Format d'email invalide"
}
Implementation Example
Here’s a complete React form implementation:
import { useState } from 'react' ;
function ContactForm () {
const [ formData , setFormData ] = useState ({
name: '' ,
email: '' ,
subject: '' ,
message: ''
});
const [ file , setFile ] = useState ( null );
const [ status , setStatus ] = useState ( '' );
const handleSubmit = async ( e ) => {
e . preventDefault ();
const data = new FormData ();
data . append ( 'name' , formData . name );
data . append ( 'email' , formData . email );
data . append ( 'subject' , formData . subject );
data . append ( 'message' , formData . message );
if ( file ) {
data . append ( 'attachment' , file );
}
try {
const response = await fetch ( 'https://your-api-domain.com/api/contact' , {
method: 'POST' ,
body: data
});
const result = await response . json ();
if ( response . ok ) {
setStatus ( 'success' );
// Reset form
setFormData ({ name: '' , email: '' , subject: '' , message: '' });
setFile ( null );
} else {
setStatus ( `error: ${ result . error } ` );
}
} catch ( err ) {
setStatus ( 'error: Network error' );
}
};
return (
< form onSubmit = { handleSubmit } >
< input
type = "text"
placeholder = "Name"
value = { formData . name }
onChange = { ( e ) => setFormData ({ ... formData , name: e . target . value }) }
required
/>
< input
type = "email"
placeholder = "Email"
value = { formData . email }
onChange = { ( e ) => setFormData ({ ... formData , email: e . target . value }) }
required
/>
< input
type = "text"
placeholder = "Subject"
value = { formData . subject }
onChange = { ( e ) => setFormData ({ ... formData , subject: e . target . value }) }
required
/>
< textarea
placeholder = "Message"
value = { formData . message }
onChange = { ( e ) => setFormData ({ ... formData , message: e . target . value }) }
required
/>
< input
type = "file"
onChange = { ( e ) => setFile ( e . target . files [ 0 ]) }
/>
< button type = "submit" > Send </ button >
{ status && < p > { status } </ p > }
</ form >
);
}
Email Template
The sent email uses a responsive HTML template with:
Header: Sabbels Handmade branding with gradient background
Sender Info: Name, email, subject, and timestamp
Message Body: Formatted with preserved line breaks
Attachment Notice: Visual indicator when file is attached
Footer: Instructions for replying to the customer
Multer Configuration
File uploads are handled by Multer:
const upload = multer ({
storage: multer . memoryStorage (),
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
});
Storage: Memory storage (file buffer is passed directly to Resend)
Limits: 10MB maximum file size
Notes
No authentication required - publicly accessible endpoint
Reply-to header is set to sender’s email for easy responses
Line breaks in messages are preserved in the email
File attachments are optional
Email sending is non-blocking (errors are caught and logged)
The endpoint returns success even in test mode without RESEND_API_KEY
Security Considerations
Spam Prevention: This endpoint is publicly accessible and could be targeted by spam bots. Consider implementing:
Rate limiting per IP address
CAPTCHA verification (reCAPTCHA, hCaptcha)
Honeypot fields
Email domain validation
Content filtering