Website Forms Integration

Zuletzt aktualisiert: Apr 27, 2026Abschnitt: General

Website Forms Integration

Connect your website contact and subscription forms to Mailoo for secure message processing and automated email campaigns.

Quick Start

  1. Create a project and form integration in your Mailoo dashboard
  2. Generate an API key and store it in server environment variables only
  3. Add the subscription form HTML to your website
  4. Implement a backend or BFF route that forwards to Mailoo; the browser posts to your route only
  5. Test your form submission

Prerequisites

Mailoo Account Setup

: You need an active Mailoo account with a project and form integration configured.

Website and server

: Ability to edit HTML and JavaScript on your site, and to deploy a server route (or BFF) that holds the API key.

API Key

: A valid API key generated from your Mailoo dashboard.

:::: important ::: title Important :::

Server-side only: Form integration must be done via your backend. The form submits data to your server (e.g. a BFF route); your server then calls the Mailoo API using credentials from environment variables. Do not use the API key in client-side code. ::::

:::: note ::: title Note :::

Legacy (not recommended): Calling Mailoo webhooks directly from the browser can work if CORS and allowed origins are configured, but it exposes the API key to anyone who can view the page. Do not use this for new integrations. ::::

Step 1: Create Your Integration

Navigate to your Mailoo dashboard and create a new form integration:

  1. Go to Dashboard → Projects → [Your Project] → Integrations
  2. Click "Create New Integration"
  3. Select "Form Integration"
  4. Configure your integration:
  • Name: Give your integration a descriptive name (e.g., "Newsletter Signup")
  • Status: Set to Active
  • Allowed origins: A list of origins (e.g. https://example.com, https://www.example.com) from which form submission requests are allowed. Add every domain that will host the form or send requests to the API. The request Origin header must match one of the entries in this list.

:::: important ::: title Important :::

Request body: The form must send only:

  • email - Email address (required)
  • name - Subscriber name (optional)

The notification (message) is not built from form fields. It is built from a message template attached to an On-Event (on create) campaign for this integration. You must create exactly one campaign of type On-Event with event On create and attach a message template to it. If no such campaign exists or the template is missing, the API returns 503 with the message Subscription form is not ready (Форма подписки не готова). ::::

:::: note ::: title Note :::

Redirect after submission: Redirecting the user after a successful form submission (e.g. to a thank-you page) is entirely the responsibility of the form on the site that uses the API --- in your form handler or backend. Mailoo does not participate in redirects and does not provide a redirect URL; after a successful API response, your code may redirect to any URL you choose. Request body: In the request body only email is required; name is optional. You may also send optional subject and content --- they are used for the created inbound message in the dashboard Inbox. If omitted, defaults are used (e.g. subject "New subscription", content "New subscription from {email}"). You do not need to create a campaign or message template for the form to accept submissions; the form is accepted whenever email is valid and CORS allows the request. ::::

:::: note ::: title Note :::

Redirect after submission: Redirecting the user after a successful form submission (e.g. to a thank-you page) is entirely the responsibility of the form on the site that uses the API --- in your form handler or backend. Mailoo does not participate in redirects and does not provide a redirect URL; after a successful API response, your code may redirect to any URL you choose. ::::

Step 2: Generate API Key

  1. In your integration settings, navigate to the API Keys section

  2. Click "Generate New API Key"

  3. Give your API key a descriptive name (e.g., "Website Forms")

  4. Important: Copy and securely store your API key - it's only shown once!

    If you use a RESTRICTED API key, it must have the form submission permission (e.g. "Form submissions (web forms)" / webhook.form-submission) to call the form webhook.

:::: warning ::: title Warning :::

Security: Never expose your API key in client-side JavaScript. For production use, implement server-side form processing. ::::

Step 3: "Stay Updated" Subscription Form

Here's a complete example of a "Stay Updated" subscription form:

HTML Structure:

<section class="newsletter-signup">
  <div class="container">
    <h2>Stay Updated</h2>
    <p>Get the latest features, tips, and mindfulness insights delivered to your inbox.</p>

    <form id="mailoo-subscribe-form" class="subscribe-form">
      <div class="form-group">
        <label for="email" class="sr-only">Enter your email</label>
        <input 
          type="email" 
          id="email" 
          name="email" 
          placeholder="Enter your email"
          required 
          class="email-input"
        />
      </div>

      <div class="form-group">
        <input 
          type="text" 
          id="name" 
          name="name" 
          placeholder="Your name (optional)"
          class="name-input"
        />
      </div>

      <button type="submit" class="subscribe-btn">
        Subscribe
      </button>
    </form>

    <div id="mailoo-status" class="status-message" style="display: none;">
      <span id="mailoo-status-text"></span>
    </div>
  </div>
</section>

CSS Styling:

.newsletter-signup {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 60px 0;
  text-align: center;
}

.subscribe-form {
  max-width: 400px;
  margin: 2rem auto 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.email-input, .name-input {
  padding: 12px 16px;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  width: 100%;
  box-sizing: border-box;
}

.subscribe-btn {
  background: #ff6b6b;
  color: white;
  border: none;
  padding: 12px 24px;
  border-radius: 8px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
}

.subscribe-btn:hover {
  background: #ff5252;
}

.subscribe-btn:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.status-message {
  margin-top: 1rem;
  padding: 10px;
  border-radius: 4px;
  text-align: center;
}

.status-success {
  background: rgba(76, 175, 80, 0.1);
  color: #4caf50;
  border: 1px solid #4caf50;
}

.status-error {
  background: rgba(244, 67, 54, 0.1);
  color: #f44336;
  border: 1px solid #f44336;
}

Step 4: JavaScript Implementation

The form must submit to your backend (e.g. /api/subscribe or your BFF route). The client never calls Mailoo or sends the API key. Example: collect the same fields and POST to your server.

document.addEventListener('DOMContentLoaded', function() {
  const form = document.getElementById('mailoo-subscribe-form');
  const statusDiv = document.getElementById('mailoo-status');
  const statusText = document.getElementById('mailoo-status-text');
  // Your backend URL (e.g. /api/subscribe or /api/v1/webhooks/forms/submit)
  const BACKEND_URL = '/api/subscribe';

  form.addEventListener('submit', async function(e) {
    e.preventDefault();
    const submitBtn = form.querySelector('.subscribe-btn');
    const originalText = submitBtn.textContent;
    submitBtn.textContent = 'Subscribing...';
    submitBtn.disabled = true;
    try {
      const formData = {
        email: form.email.value.trim(),
        name: form.name.value.trim() || undefined,
        source: window.location.href,
        metadata: {
          type: 'newsletter_subscription',
          timestamp: new Date().toISOString(),
          userAgent: navigator.userAgent,
          referrer: document.referrer || 'direct'
        }
      };
      if (!formData.email) throw new Error('Email is required');
      const response = await fetch(BACKEND_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData)
      });
      const result = await response.json();
      if (response.ok && result.success) {
        showStatus('success', 'Thank you for subscribing!');
        form.reset();
        if (typeof gtag !== 'undefined') {
          gtag('event', 'newsletter_signup', { event_category: 'engagement', event_label: 'success' });
        }
      } else {
        const msg = result.message || result.error || 'Subscription failed';
        if (response.status === 503 && msg.includes('not ready')) {
          showStatus('error', 'Subscription form is not ready. Please try again later or contact support.');
        } else {
          throw new Error(msg);
        }
      }
    } catch (error) {
      console.error('Subscription error:', error);
      showStatus('error', 'Subscription failed. Please try again or contact us directly.');
      if (typeof gtag !== 'undefined') {
        gtag('event', 'newsletter_signup_error', { event_category: 'error', event_label: error.message });
      }
    } finally {
      submitBtn.textContent = originalText;
      submitBtn.disabled = false;
    }
  });
  function showStatus(type, message) {
    statusText.textContent = message;
    statusDiv.className = 'status-message status-' + type;
    statusDiv.style.display = 'block';
    if (type === 'success') setTimeout(function() { statusDiv.style.display = 'none'; }, 5000);
  }
});

Step 5: Configuration

Configuration (API URL, project UID, API key) is set only on the server in environment variables. The client does not need Mailoo URLs or keys. Implement your backend as in the Secure Server-Side Implementation section below: Node.js/Express example or website-forms-nextjs-example{.interpreted-text role="doc"} for Next.js.

Secure Server-Side Implementation

This is the only recommended approach: the form posts to your server, and your server calls the Mailoo API with credentials from environment variables. For Next.js, see website-forms-nextjs-example{.interpreted-text role="doc"}. Below is a Node.js/Express example.

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/subscribe', async (req, res) => {
  try {
    const { email, name } = req.body;

    // Validate required fields
    if (!email || !email.includes('@')) {
      return res.status(400).json({ 
        error: 'Valid email is required' 
      });
    }

    // Submit to Mailoo (project UID and integration ID from dashboard)
    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 || 'server-side-form',
          metadata: {
            type: 'newsletter_subscription',
            ip: req.ip,
            userAgent: req.headers['user-agent'],
            timestamp: new Date().toISOString()
          }
        })
      }
    );

    const result = await response.json();

    if (response.ok && result.success) {
      res.json({ 
        success: true, 
        message: 'Subscription successful!' 
      });
    } else if (response.status === 503 && result.message?.includes('not ready')) {
      res.status(503).json({ error: 'Subscription form is not ready. Please try again later or contact support.' });
    } else {
      throw new Error(result.message || 'Subscription failed');
    }

  } catch (error) {
    console.error('Subscription error:', error);
    res.status(500).json({ 
      error: 'Subscription failed. Please try again.' 
    });
  }
});

Environment Variables (.env):

MAILOO_API_URL=https://api.mailoo.app/api/v1
PROJECT_UID=your-project-uid-here
INTEGRATION_ID=your-form-integration-id-here
MAILOO_API_KEY=your-api-key-here
WEBSITE_ORIGIN=https://yourdomain.com

The integration ID is the form integration's ID. Find it in the dashboard URL when viewing the integration (e.g. /dashboard/projects/{projectUid}/integrations/{integrationId}) or from the project's integrations list.

Browser handler (posts to your backend only):

// Form handler: same-origin POST; no Mailoo URL or API key in the page
form.addEventListener('submit', async function(e) {
  e.preventDefault();

  const submitBtn = form.querySelector('.subscribe-btn');
  const originalText = submitBtn.textContent;
  submitBtn.textContent = 'Subscribing...';
  submitBtn.disabled = true;

  try {
    const response = await fetch('/api/subscribe', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: form.email.value.trim(),
        name: form.name.value.trim()
      })
    });

    const result = await response.json();

    if (response.ok && result.success) {
      showStatus('success', 'Thank you for subscribing!');
      form.reset();
    } else {
      throw new Error(result.error || 'Subscription failed');
    }

  } catch (error) {
    showStatus('error', 'Subscription failed. Please try again.');
  } finally {
    submitBtn.textContent = originalText;
    submitBtn.disabled = false;
  }
});

Testing Your Integration

  1. Test Form Submission Fill out your subscription 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. The URL uses the integration ID (not the project UID). You can find the integration ID in the dashboard URL when viewing the integration (e.g. /dashboard/projects/{projectUid}/integrations/{integrationId}) or from the project's integrations list via the API.

    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.

  4. Verify Email Collection Confirm that email addresses are being stored properly in your Mailoo message inbox.

Troubleshooting

401 - Invalid API Key

: - Verify your API key is correct and hasn't been regenerated

  • Ensure the API key is active in your dashboard
  • Check that you're using the correct project UID

403 - Origin Not Allowed

: - Allowed origins are configured as a list in the integration settings. Add the origin of the page that submits the form (scheme + host, e.g. https://yourdomain.com) to that list.

  • Ensure the request Origin header matches one of the allowed origins exactly (including protocol).

429 - Rate Limited

: - Too many requests from the same API key

  • Wait before retrying; add backoff or queuing on your server for high traffic

CORS Errors

: - The API allows requests whose Origin matches one of the allowed origins in the integration settings. Add the origin of the page that sends the form to the allowed-origins list (scheme + host).

  • Verify the Origin header is being sent correctly and that protocol and port match.

Form Validation Errors

: - Ensure email field contains valid email address

  • Check that required fields (name, email, message) are not empty
  • Verify field lengths don't exceed maximum limits

Advanced Usage

Multiple Form Types

: Use the same integration for different forms by adding identifiers:

``` javascript
metadata: {
  formType: 'newsletter',        // or 'contact', 'support'
  pageUrl: window.location.href,
  formId: 'newsletter-sidebar'
}
```

Custom Success Actions

: Handle successful submissions with custom actions:

``` javascript
if (response.ok && result.success) {
  // Show thank you modal
  showThankYouModal();

  // Redirect to thank you page
  setTimeout(() => {
    window.location.href = '/thank-you';
  }, 2000);

  // Add to email list for further marketing
  addToEmailList(formData.email);
}
```

Enhanced Analytics

: Track detailed subscription metrics:

``` javascript
// Track subscription source
gtag('event', 'newsletter_signup', {
  event_category: 'engagement',
  event_label: 'homepage-hero',
  custom_parameters: {
    form_location: 'above-fold',
    user_type: 'first-time-visitor'
  }
});
```

API Reference

The following describes the Mailoo endpoint your server calls after receiving data from the browser. Do not use this URL from public frontend code with a live API key.

Form Submission Endpoint

POST /api/v1/webhooks/forms/{projectUid}/{integrationId}

Both project UID and integration ID are required in the path. Find the integration ID in the dashboard when viewing the form integration (URL or integrations list).

Validation Approach

The webhook endpoint uses manual validation in the controller for better error handling and compatibility. This ensures reliable form processing with detailed error messages for debugging.

Request Headers:


Header Required Description


Content-Type Yes application/json

X-API-Key Yes Your project API key

Request Body:

  • email (required) --- Subscriber email address.
  • name (optional) --- Subscriber name.
  • subject (optional) --- Subject of the created inbound message in the dashboard; if omitted, default is "New subscription".
  • content (optional) --- Body of the created inbound message; if omitted, a default string is used (e.g. "New subscription from {email}").
  • source, metadata (optional) --- Passed through to message metadata.
{
  "email": "john@example.com",
  "name": "John Doe",
  "source": "https://yoursite.com/newsletter",
  "metadata": {
    "type": "newsletter_subscription",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

Response Format:

For a new subscriber (email not yet in the integration), the API creates a message and returns:

{
  "success": true,
  "messageId": "msg_abc123def456",
  "message": "Form submission processed successfully"
}

For an existing subscriber (same email already in the integration), no new message is created. The subscriber record is updated only by resetting unsubscribedAt to null (re-subscription; the name is not changed). The response omits messageId:

{
  "success": true,
  "message": "Already subscribed"
}

Error Response:

Validation errors from this endpoint return only error and message (no details array). Examples: "Email is required", "Invalid email format".

If the subscription form is not ready (no On-Event campaign with event "On create" and a message template configured for this integration), the API returns 503 with:

{
  "message": "Subscription form is not ready"
}

Show this to the user (e.g. "Subscription form is not ready. Please try again later or contact support.") and ensure in the dashboard that exactly one On-Event campaign with event On create exists for this integration and has a message template attached.

Subscribers and Templates (Dashboard Only)

Subscribers

: The list of subscribers (email, name, date created) is available only in the Mailoo dashboard. In the subscriber list, each row has Unsubscribe (for active subscribers) and Delete (with inline confirmation in the same row). In the list of unsubscribed users, Restore is available to resubscribe. External sites do not have an API to read the subscriber list; they only submit new signups via the form webhook. If the same email submits the form again, no new message is created in the inbox; only the subscriber's unsubscribedAt is cleared (name is unchanged), and the API response does not include messageId.

Message templates

: Message templates (subject, content, HTML) are managed only in the Mailoo dashboard. They are used when creating campaigns (e.g. to prefill subject and body) and when replying to inbound messages in the dashboard (Reply uses an On-Event campaign template with Mustache and User context). The subscription form does not require a template to accept submissions; the inbound message is built from the request body (or defaults). Create an On-Event campaign with a message template if you want to reply to incoming messages from the dashboard using that template. There is no API for external sites to read or manage templates.

Unsubscribe (For External Sites)

External sites need to let users unsubscribe. Mailoo provides:

  1. One-click unsubscribe page --- A link in your emails sends the user to a Mailoo-hosted confirmation page. When they open it, their email is marked as unsubscribed (no login required).
  2. Unsubscribe API --- Your server can record an unsubscribe by calling the Mailoo API with the subscriber's email and integration ID (and optionally a signed token).

Token-based unsubscribe (recommended for links in emails)

To build a safe one-click link, you need a token that only Mailoo can verify. The token is an HMAC-SHA256 signature of email:integrationId (lowercase email, colon, integration ID), encoded in base64url. The secret is set in the Mailoo environment as UNSUBSCRIBE_SECRET.

Example: Unsubscribe link in an email

Your backend generates the token when sending the email, then you put this URL in the email (e.g. in the footer):

https://mailoo.app/{locale}/unsubscribe/confirm?email={email}&integrationId={integrationId}&token={token}

Replace {locale} with the user's locale (e.g. en), {email} with the subscriber's email (URL-encoded), {integrationId} with your form integration ID from the dashboard, and {token} with the base64url HMAC.

Example: Generating the token (Node.js)

const crypto = require('crypto');

function getUnsubscribeToken(email, integrationId, secret) {
  const payload = `${email.toLowerCase().trim()}:${integrationId}`;
  return crypto.createHmac('sha256', secret).update(payload).digest('base64url');
}

// When sending an email (server-side):
const token = getUnsubscribeToken(subscriber.email, process.env.MAILOO_INTEGRATION_ID, process.env.UNSUBSCRIBE_SECRET);
const link = `https://mailoo.app/en/unsubscribe/confirm?email=${encodeURIComponent(subscriber.email)}&integrationId=${integrationId}&token=${token}`;

:::: note ::: title Note :::

UNSUBSCRIBE_SECRET must be the same value configured in Mailoo (so Mailoo can verify the token). For security, only your server and Mailoo should know this secret; do not expose it to the client. ::::

Example: Server-side unsubscribe (API)

If you prefer to record unsubscribes from your own backend (e.g. after the user clicks "Unsubscribe" on your site), call the webhook with your API key:

POST /api/v1/webhooks/unsubscribe
Content-Type: application/json
X-API-Key: your-api-key

{"email": "user@example.com", "integrationId": "your-integration-id"}

Response: 200 OK with {"success": true, "message": "Unsubscribed successfully"}.

Dashboard

: In the Mailoo dashboard you can unsubscribe or delete a subscriber from the subscriber list (Unsubscribe / Delete per row; Delete uses inline confirmation). The Unsubscribes tab lists everyone who has unsubscribed (email, name, date); there you can Restore a subscriber so they receive mail again.

Campaigns (Dashboard, Manual)

Campaigns are created and managed in the Mailoo dashboard (Messages → select a form integration → Campaigns tab). You can:

  • Create a campaign from a message template (subject and content are copied from the template).
  • Add recipients from the subscriber list (only active subscribers; unsubscribed addresses are excluded).
  • Set send date (immediate or scheduled). Actual sending is manual in the current version (no automatic scheduler).

Subscribers and templates are only available in the dashboard; the campaign flow uses the same integration and subscriber data collected via the form webhook.

Security Best Practices

  1. API Key Security
  • Never expose API keys in client-side code
  • Use environment variables for server-side implementations
  • Rotate API keys regularly
  • Use different keys for development and production
  1. Input Validation
  • Validate email addresses on both client and server
  • Sanitize user input before processing
  • Set appropriate field length limits
  • Implement rate limiting on your forms
  1. CORS Configuration
  • Configure the allowed origins list in the integration settings with every origin (protocol + host) that will submit the form (e.g. https://yourdomain.com, https://www.yourdomain.com).
  • Validate Origin headers server-side; the request origin must match one of the allowed origins.
  1. Data Privacy
  • Comply with GDPR and other privacy regulations
  • Provide clear opt-in mechanisms
  • Include unsubscribe options in all communications
  • Secure transmission with HTTPS

Next Steps

Once your form integration is working:

  1. Dashboard: View inbound messages in Inbox, reply using a template (Reply), and see outgoing messages in Outbox and Sent.
  2. Set Up Automation: Configure automated welcome emails and drip campaigns
  3. Create Email Templates: Design professional email templates for your communications
  4. Monitor Performance: Track subscription rates and engagement metrics
  5. A/B Testing: Test different form designs and copy to optimize conversions

Need Help?

If you encounter issues or have questions:

The integration should now be collecting email addresses securely and enabling you to build your subscriber list effectively.