Website Forms Integration

Connect your website contact forms to Mailoo for secure message processing and automated responses

Quick Start

  1. 1. Create a project and FORM integration in your Mailoo dashboard
  2. 2. Create an API key and store it in server environment variables only
  3. 3. Add the form HTML to your website
  4. 4. Add a backend or BFF route that forwards submissions to Mailoo
  5. 5. Add a small browser script that POSTs to your own route (never to Mailoo with a key)
  6. 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] → Settings

Security: 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_URLhttps://api.mailoo.app/api/v1 (production)
PROJECT_UIDYour project UID from dashboard
INTEGRATION_IDYour form integration ID (from dashboard when viewing the integration)
MAILOO_API_KEYAPI key used only in server-side forwarder code
WEBSITE_ORIGINPublic 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.com

API 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

HeaderRequiredDescription
Content-TypeYesapplication/json
X-API-KeyYesYour project API key
OriginRecommendedFor 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.