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
| Type | Name | Value | Purpose |
|---|---|---|---|
MX | @ | mail.maileto.ir (pri 10) | Receive email |
TXT | @ | v=spf1 mx ~all | SPF — authorize sending |
TXT | _dmarc | v=DMARC1; p=none; | DMARC policy |
TXT | dkim._domainkey | Shown in domain settings | DKIM signature |
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
Login: your full email address (e.g. info@yourdomain.com)
Pass: the password you set when creating the mailbox
Email Client Settings (IMAP)
| Setting | Value |
|---|---|
| IMAP Server | mail.maileto.ir |
| IMAP Port | 993 (SSL/TLS) |
| SMTP Server | mail.maileto.ir |
| SMTP Port | 587 (STARTTLS) |
| Username | Full email address |
| Password | Mailbox 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.
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
- Create a campaign — set name, subject, from address, and HTML body.
- Add recipients — import a contact list or paste emails.
- Send a test — use Send Test to preview in a real inbox.
- Schedule or send immediately.
Campaign Status
| Status | Meaning |
|---|---|
| draft | Not yet sent, still editable |
| scheduled | Will be sent at the scheduled time |
| sending | Currently being delivered |
| sent | All 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
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
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.
| Variable | Replaced 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 |
{{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.
| Metric | Definition |
|---|---|
| Sent | Total messages accepted by the mail server |
| Delivered | Accepted by the recipient's server |
| Opened | Tracking pixel loaded (requires HTML email) |
| Clicked | Any tracked link clicked |
| Bounced | Rejected by recipient server (hard bounce) |
| Unsubscribed | Recipient 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
| Event | Trigger |
|---|---|
open | Recipient opens the email |
click | Recipient clicks a tracked link |
bounce | Email was rejected or undeliverable |
complaint | Recipient marks email as spam |
unsubscribe | Recipient clicks the unsubscribe link |
ping | Test 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+eventas 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.