Documentation

Everything you need to get started with Maileto.

Overview

Maileto is an email infrastructure platform that lets you manage domains, mailboxes, and email sending from one place. It connects to a Mailcow mail server on the backend, so every mailbox you create is a real, fully functional email account.

📧 Mailboxes

Real IMAP/SMTP accounts your team can use in any email client.

📣 Campaigns

Send bulk marketing emails to your contact lists with tracking.

🔌 API & SMTP

Send transactional emails from your app via API or SMTP relay.

Domains

Add your domain first — everything else (mailboxes, aliases, campaigns) belongs to a domain. After adding a domain you will receive a set of DNS records to publish in your DNS provider.

Required DNS Records

TypeNameValuePurpose
MX@mail.maileto.ir (pri 10)Receive email
TXT@v=spf1 mx ~allSPF — authorize sending
TXT_dmarcv=DMARC1; p=none;DMARC policy
TXTdkim._domainkeyShown in domain settingsDKIM signature
DNS propagation can take up to 48 hours. Maileto checks automatically — the domain status turns green once all records are verified.

Mailboxes

Mailboxes are full email accounts (IMAP + SMTP). Each mailbox has its own password, quota, and can be accessed from any standard email client (Outlook, Thunderbird, Apple Mail) or via webmail at mail.maileto.ir.

Webmail Access

URL: https://mail.maileto.ir

Login: your full email address (e.g. info@yourdomain.com)

Pass: the password you set when creating the mailbox

Email Client Settings (IMAP)

SettingValue
IMAP Servermail.maileto.ir
IMAP Port993 (SSL/TLS)
SMTP Servermail.maileto.ir
SMTP Port587 (STARTTLS)
UsernameFull email address
PasswordMailbox password

Mailbox Actions

  • Enable / Disable — temporarily block access without deleting
  • Change Password — from the mailbox detail page
  • Forwards — auto-forward incoming mail to another address
  • Delete — permanently removes the mailbox and all stored mail

Aliases

An alias is an address that redirects to one or more real mailboxes. Use aliases to create addresses like support@ or hello@ without creating separate mailbox accounts.

support@yourdomain.com → info@yourdomain.com, admin@yourdomain.com

Catch-all aliases (address = @yourdomain.com) capture any email sent to an undefined address on your domain.

Campaigns

Campaigns let you send bulk HTML emails to a list of contacts. Each campaign tracks opens, clicks, unsubscribes, and bounces.

Workflow

  1. Create a campaign — set name, subject, from address, and HTML body.
  2. Add recipients — import a contact list or paste emails.
  3. Send a test — use Send Test to preview in a real inbox.
  4. Schedule or send immediately.

Campaign Status

StatusMeaning
draftNot yet sent, still editable
scheduledWill be sent at the scheduled time
sendingCurrently being delivered
sentAll recipients processed

Contacts

Contacts are grouped into lists. A list is added to a campaign as the recipient pool. You can import contacts from a CSV file or add them one by one.

CSV Import Format

email,first_name,last_name john@example.com,John,Doe jane@example.com,Jane,Smith

Only email is required. first_name and last_name are optional and available as template variables.

Email Templates

Save reusable HTML email designs as templates. Apply a template when creating a campaign to pre-fill the HTML and text body. Templates support all the same variables as campaigns.

SMTP Credentials

Generate SMTP credentials to send transactional email directly from your application — password reset links, order confirmations, notifications.

Host: mail.maileto.ir

Port: 587 (STARTTLS)

Username: generated in SMTP settings

Password: shown once at creation

From: any address on your verified domain

The password is only shown once. Copy it immediately and store it securely.

API Keys

Use the REST API to send transactional emails programmatically. Generate an API key from API Keys in the sidebar.

Send an Email via API

POST https://maileto.ir/api/v1/send
Content-Type: application/json
X-API-Key: YOUR_API_KEY

{
  "from": "info@yourdomain.com",
  "to": ["user@example.com"],
  "subject": "Hello from Maileto",
  "html": "<p>Your message here.</p>",
  "text": "Your message here."
}

Response

{
  "message_id": "abc123@maileto.ir",
  "status": "queued"
}

Template Variables

Use these variables in campaign subject lines and HTML body. They are replaced per-recipient at send time.

VariableReplaced with
{{first_name}}Recipient's first name
{{last_name}}Recipient's last name
{{email}}Recipient's email address
{{unsubscribe_url}}One-click unsubscribe link (required by law in marketing emails)
{{tracking_pixel}}Invisible 1×1 pixel for open tracking
Always include {{unsubscribe_url}} in marketing campaigns. Missing unsubscribe links violate anti-spam laws (CAN-SPAM, GDPR).

Analytics

The Analytics page shows aggregate metrics across all campaigns and transactional sends.

MetricDefinition
SentTotal messages accepted by the mail server
DeliveredAccepted by the recipient's server
OpenedTracking pixel loaded (requires HTML email)
ClickedAny tracked link clicked
BouncedRejected by recipient server (hard bounce)
UnsubscribedRecipient clicked unsubscribe link

Webhooks

Webhooks let your server react to email events in real time. When an event occurs, Maileto sends an HTTP POST request to your endpoint with a JSON body. Your server must respond with a 2xx status code within 10 seconds. Failed deliveries are retried up to 3 times: after 5 min → 30 min → 2 hr.

Request Headers

POST https://your-server.com/webhooks/maileto
Content-Type: application/json
X-Maileto-Signature: sha256=<hmac-sha256-hex>
User-Agent: Maileto-Webhook/1.0

Event Types

EventTrigger
openRecipient opens the email
clickRecipient clicks a tracked link
bounceEmail was rejected or undeliverable
complaintRecipient marks email as spam
unsubscribeRecipient clicks the unsubscribe link
pingTest event triggered via the Ping button

Payload Envelope

{
  "event":     "<event_type>",
  "timestamp": "2026-06-13T10:00:00+00:00",
  "data":      { ... }
}

Example Payloads

open

{
  "event": "open",
  "timestamp": "2026-06-13T10:00:00+00:00",
  "data": {
    "message_id":     "<abc123@maileto.ir>",
    "tracking_token": "tok_abc123",
    "from":           "hello@yourdomain.com",
    "to":             ["recipient@example.com"],
    "subject":        "Your order is confirmed"
  }
}

click

{
  "event": "click",
  "timestamp": "2026-06-13T10:00:00+00:00",
  "data": {
    "message_id":     "<abc123@maileto.ir>",
    "tracking_token": "tok_abc123",
    "url":            "https://yoursite.com/promo",
    "from":           "hello@yourdomain.com",
    "to":             ["recipient@example.com"],
    "subject":        "Your order is confirmed"
  }
}

bounce

{
  "event": "bounce",
  "timestamp": "2026-06-13T10:00:00+00:00",
  "data": {
    "message_id": "<abc123@maileto.ir>",
    "from":       "hello@yourdomain.com",
    "to":         ["bad-address@example.com"],
    "subject":    "Your order is confirmed"
  }
}

unsubscribe

{
  "event": "unsubscribe",
  "timestamp": "2026-06-13T10:00:00+00:00",
  "data": {
    "message_id":     "<abc123@maileto.ir>",
    "tracking_token": "tok_abc123",
    "email":          "recipient@example.com",
    "from":           "hello@yourdomain.com",
    "subject":        "Your order is confirmed"
  }
}

Verifying the Signature

The X-Maileto-Signature header is sha256=<HMAC-SHA256 hex> computed over the raw request body using your endpoint's signing secret (shown once at creation, prefixed whsec_). Always verify before processing.

PHP

<?php
$secret   = 'whsec_your_signing_secret';
$body     = file_get_contents('php://input');
$sig      = $_SERVER['HTTP_X_MAILETO_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);

if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($body, true);

Node.js (Express)

const crypto = require('crypto');

app.post('/webhooks/maileto', express.raw({ type: 'application/json' }), (req, res) => {
    const secret   = process.env.WEBHOOK_SECRET;
    const sig      = req.headers['x-maileto-signature'] ?? '';
    const expected = 'sha256=' + crypto
        .createHmac('sha256', secret)
        .update(req.body)
        .digest('hex');

    if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
        return res.status(401).send('Invalid signature');
    }

    const event = JSON.parse(req.body);
    // handle event asynchronously...
    res.sendStatus(200);
});

Python (Flask)

import hmac, hashlib
from flask import Flask, request, abort

app    = Flask(__name__)
SECRET = b'whsec_your_signing_secret'

@app.route('/webhooks/maileto', methods=['POST'])
def webhook():
    sig      = request.headers.get('X-Maileto-Signature', '')
    expected = 'sha256=' + hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        abort(401)
    event = request.get_json()
    # handle event asynchronously...
    return '', 200

Best Practices

  • Respond immediately with 200. Offload processing to a queue — handlers that exceed 10 s are marked failed.
  • Always verify the signature. Reject requests with an invalid X-Maileto-Signature.
  • Handle duplicates. Retries can deliver the same event more than once. Use message_id + event as an idempotency key.
  • Use HTTPS. HTTP is accepted but not recommended in production.
  • Guard your secret. Never expose whsec_* in client-side code or public repositories.

Suppression List

Addresses on the suppression list are automatically skipped in all future campaigns. Maileto adds addresses here automatically on hard bounce or unsubscribe. You can also add addresses manually.

Respecting the suppression list keeps your sending reputation healthy and is legally required for unsubscribed recipients.