Website Forms Integration
Connect your website contact forms to Mailoo for secure message processing and automated responses
Quick Start
- 1. Create a project and FORM integration in your Mailoo dashboard
- 2. Create an API key and store it in server environment variables only
- 3. Add the form HTML to your website
- 4. Add a backend or BFF route that forwards submissions to Mailoo
- 5. Add a small browser script that POSTs to your own route (never to Mailoo with a key)
- 6. Test your form submission
Prerequisites
Mailoo Account Setup
You need an active Mailoo account with a project and email integration configured.
Website and server
Ability to edit HTML and JavaScript on your site, and to deploy a server route (or serverless/BFF) that holds your API key.
Step 1: Get Your API Key
In the Mailoo dashboard, open your project → API keys (or integration settings) and create a key. Use it only on the server—never in browser JavaScript or public repos.
Dashboard → [Your Project] → [Your Integration] → SettingsSecurity: Keep your API key secure and never expose it in client-side code.
Step 2: Create Your HTML Form
Add this HTML form to your website. The form will capture user messages and send them to Mailoo:
<form id="mailoo-contact-form" class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium">Name *</label>
<input
type="text"
id="name"
name="name"
required
maxlength="100"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
/>
</div>
<div>
<label for="email" class="block text-sm font-medium">Email *</label>
<input
type="email"
id="email"
name="email"
required
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
/>
</div>
<div>
<label for="subject" class="block text-sm font-medium">Subject</label>
<input
type="text"
id="subject"
name="subject"
maxlength="200"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
/>
</div>
<div>
<label for="message" class="block text-sm font-medium">Message *</label>
<textarea
id="message"
name="message"
required
maxlength="5000"
rows="5"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
></textarea>
</div>
<button
type="submit"
class="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Send Message
</button>
</form>
<div id="mailoo-status" style="display: none;" class="mt-4 p-3 rounded-md">
<span id="mailoo-status-text"></span>
</div>Step 3: Browser handler (your site only)
The browser should call a URL on your origin (for example a Next.js Route Handler or Express route). That server code adds X-API-Key and forwards to Mailoo. Do not put the Mailoo API URL or API key in this script.
Legacy (not recommended): Calling Mailoo directly from the browser can work if CORS and allowed origins are configured, but it exposes your key to anyone who can view the page source. We do not recommend this for new integrations.
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('mailoo-contact-form');
const statusDiv = document.getElementById('mailoo-status');
const statusText = document.getElementById('mailoo-status-text');
// Your backend route — same origin, no API key in the page
const YOUR_SUBMIT_URL = '/api/contact/submit';
form.addEventListener('submit', async function(e) {
e.preventDefault();
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Sending...';
submitBtn.disabled = true;
try {
const formData = {
email: form.email.value.trim(),
name: form.name.value.trim() || undefined,
source: window.location.href,
metadata: {
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
referrer: document.referrer
}
};
const response = await fetch(YOUR_SUBMIT_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const result = await response.json();
if (response.ok && result.success) {
showStatus('success', 'Message sent successfully! We\'ll get back to you soon.');
form.reset();
} else if (response.status === 503 && result.message?.includes('not ready')) {
showStatus('error', 'Form is not ready. Please try again later or contact support.');
} else {
throw new Error(result.message || 'Failed to send message');
}
} catch (error) {
console.error('Form submission error:', error);
showStatus('error', 'Failed to send message. Please try again or contact us directly.');
} finally {
submitBtn.textContent = originalText;
submitBtn.disabled = false;
}
});
function showStatus(type, message) {
statusText.textContent = message;
statusDiv.className = 'mt-4 p-3 rounded-md ' + (
type === 'success'
? 'bg-green-100 text-green-800 border border-green-200'
: 'bg-red-100 text-red-800 border border-red-200'
);
statusDiv.style.display = 'block';
setTimeout(function() { statusDiv.style.display = 'none'; }, 5000);
}
});
</script>Implement YOUR_SUBMIT_URL using the server example in Secure Server-Side Implementation below (adjust the path to match, e.g. app.post('/api/contact/submit', …)).
Step 4: Configuration
Server environment variables
Set these only on the server that forwards to Mailoo—not inNEXT_PUBLIC_* or client bundles.
| MAILOO_API_URL | https://api.mailoo.app/api/v1 (production) |
| PROJECT_UID | Your project UID from dashboard |
| INTEGRATION_ID | Your form integration ID (from dashboard when viewing the integration) |
| MAILOO_API_KEY | API key used only in server-side forwarder code |
| WEBSITE_ORIGIN | Public site origin sent as Origin header to Mailoo |
Optional Fields
You can add custom fields to collect additional information:
<!-- Add to your form -->
<div>
<label for="company" class="block text-sm font-medium">Company</label>
<input type="text" id="company" name="company" maxlength="100" />
</div>
<div>
<label for="phone" class="block text-sm font-medium">Phone</label>
<input type="tel" id="phone" name="phone" maxlength="20" />
</div>
<!-- Include in JSON body you POST to your backend -->
metadata: {
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
referrer: document.referrer,
company: form.company?.value || '',
phone: form.phone?.value || ''
}Security Best Practices
API Key Security
- Never expose API keys in client-side JavaScript
- Use environment variables in server-side implementations
- Rotate API keys regularly
- Restrict API key permissions to minimum required
CORS and Origin
When your servercalls Mailoo, set an Origin (or Referer) that matches your integration's allowed origins. Direct browser calls to Mailoo also rely on this (legacy); the recommended path avoids exposing the key by using your backend first.
Rate Limiting
Mailoo rate-limits webhook traffic. Debounce or queue submissions on your server if you expect bursts; avoid hammering the API from many tabs without a backend.
Input Validation
Always validate form inputs on both client and server side. Use appropriate maxlength attributes and sanitize user input before display.
Secure Server-Side Implementation
This is the recommended approach: your server (or BFF) receives the form JSON, validates it, and calls Mailoo with X-API-Key from environment variables. Align the route path with YOUR_SUBMIT_URL in Step 3.
Node.js / Express example
// server.js
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post('/api/contact/submit', async (req, res) => {
try {
const { email, name } = req.body;
// Validate required fields (only email is required)
if (!email || !email.includes('@')) {
return res.status(400).json({
error: 'Valid email is required'
});
}
// Submit to Mailoo (project UID and integration ID required)
const response = await fetch(`${process.env.MAILOO_API_URL}/webhooks/forms/${process.env.PROJECT_UID}/${process.env.INTEGRATION_ID}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.MAILOO_API_KEY,
'Origin': process.env.WEBSITE_ORIGIN
},
body: JSON.stringify({
email: email.trim(),
name: name?.trim() || undefined,
source: req.headers.referer,
metadata: {
ip: req.ip,
userAgent: req.headers['user-agent']
}
})
});
const result = await response.json();
if (response.ok && result.success) {
res.json({ success: true, message: 'Message sent successfully' });
} else {
throw new Error(result.message || 'Failed to send message');
}
} catch (error) {
console.error('Contact form error:', error);
res.status(500).json({
error: 'Failed to send message'
});
}
});Environment Variables
# .env
MAILOO_API_URL=https://api.mailoo.app/api/v1
PROJECT_UID=proj_your_project_uid
INTEGRATION_ID=your_form_integration_id
MAILOO_API_KEY=mai_your_api_key_here
WEBSITE_ORIGIN=https://yourdomain.comAPI Reference
The following describes the Mailoo API your server calls after receiving data from the browser. Do not call this URL directly from public frontend code with a live API key.
Form Submission Endpoint
POST /api/v1/webhooks/forms/{projectUid}/{integrationId}Server-to-server submission. Project UID and integration ID are required in the path.
Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Yes | application/json |
| X-API-Key | Yes | Your project API key |
| Origin | Recommended | For CORS validation |
Request Body
{
"name": "John Doe", // Required: 1-100 chars
"email": "john@example.com", // Required: valid email
"subject": "Contact inquiry", // Optional: 1-200 chars
"message": "Hello, I need help...", // Required: 1-5000 chars
"source": "https://yoursite.com/contact", // Optional: source URL
"metadata": { // Optional: additional data
"company": "Acme Corp",
"phone": "+1234567890"
}
}Response Format
// Success (200)
{
"success": true,
"messageId": "msg_abc123def",
"message": "Form submission processed successfully"
}
// Error (400/401/403/429)
{
"error": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Invalid email format",
"code": "invalid_string"
}
]
}Testing Your Integration
1. Test Form Submission
Fill out your form and submit it. Check your Mailoo dashboard for the new message in the integration inbox.
2. Check Integration Status
Use the status endpoint to verify your integration is active:
curl -X GET "https://api.mailoo.app/api/v1/webhooks/status/{integrationId}" \
-H "X-API-Key: your-api-key"3. Monitor Logs
Check your browser's developer console and network tab for any JavaScript errors or failed requests.
Troubleshooting
401 - Invalid API Key
Verify your API key is correct and the integration is active in your dashboard.
403 - Origin Not Allowed
Add your website domain to the allowed origins list in your integration settings.
429 - Rate Limited
Too many requests. Wait before retrying; add backoff or queuing on your server if traffic is high.
CORS Errors
Ensure your domain is whitelisted and the Origin header matches exactly (including protocol and port).
Advanced Usage
Multiple Forms
You can use the same integration for multiple forms on your website:
// Add form identifier in the JSON you send to your backend
metadata: {
formType: 'contact',
pageUrl: window.location.href,
formId: 'contact-page-form'
}Custom Success/Error Handling
// Custom success handler
if (response.ok && result.success) {
// Redirect to thank you page
window.location.href = '/thank-you';
// Or show custom modal
showCustomModal('Thank you for contacting us!');
// Or trigger analytics event
gtag('event', 'form_submit', {
event_category: 'contact',
event_label: 'success'
});
}Form Validation
// Client-side validation before submission (only email required; name optional)
function validateForm(formData) {
const errors = [];
if (!formData.email.trim() || !isValidEmail(formData.email)) {
errors.push('Valid email is required');
}
return errors;
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}Next Steps
Your form integration is ready! Consider setting up automated responses and email campaigns to engage with your form submissions.
Need Help?
If you run into issues or have questions about the integration, our support team is here to help.