MailWizz Setup Guide

Overview & architecture

Your BuildYourMailer install (Postal) is the delivery engine — IPs, DKIM, sending. MailWizz is the marketing app — lists, campaigns, autoresponders, subscribers. Keep the two on two separate servers:

  • Isolation of reputation — the app box runs PHP/MySQL and a public login. If it's ever probed or compromised, your sending IPs stay clean and untouched on the mail server.
  • Smaller attack surface — the mail server runs only what it needs; the web app runs elsewhere.
  • Scale independently — add app resources or more sending IPs without touching the other side.

MailWizz talks to Postal through its HTTP API (MailWizz's built-in "Postal Web Api" delivery-server driver), not SMTP. That's a deliberate choice, not just a technical detail: MailWizz never becomes a visible SMTP hop in your mail headers, so only the Postal server carries any sending reputation risk. Your marketing app is never directly exposed to the internet for mail purposes — it only ever talks to your own Postal server. Bounces and complaints flow back the same way, over a webhook, with no bounce mailbox to manage.

This guide assumes you already have a working BuildYourMailer / Postal server (installed with the one-line installer). It covers the MailWizz side, the Postal-side pieces the connection needs, and the connection between them — not the base Postal install itself.

1. Buy MailWizz on CodeCanyon

MailWizz is a commercial, self-hosted application sold on CodeCanyon (Envato Market). You buy it once and host it yourself — no monthly SaaS fee.

Get MailWizz on CodeCanyon

Which license? Regular License is right for almost everyone — you use MailWizz to send your own (and your clients') email. Choose Extended only if end users will pay to access a product built on MailWizz (a paid SaaS you resell). When in doubt, Regular.

After purchase, from your Envato Downloads page grab two things:

  • The application archive (the installable ZIP — pick "All files & documentation").
  • Your Purchase Code — a license key you enter during the MailWizz install (used for updates).

2. The server & DNS

Spin up a second VPS — do not reuse the Postal box.

  • OS: Debian 13 (64-bit), clean install.
  • Size: 2 vCPU / 4 GB RAM / 40 GB SSD is a comfortable start; more RAM helps big lists and MySQL.
  • Access: root via SSH (ideally an SSH key).

Pick a subdomain for the app, e.g. app.yourdomain.com, and add a DNS record to the new server's IP:

TypeNameValue
Aapp.yourdomain.comnew server IPv4
AAAA (optional)app.yourdomain.comnew server IPv6
Don't have a second VPS yet? We use Contabo for this box too — NVMe VPS plans from a few €/mo, in datacenters worldwide. This one doesn't need the clean-IP/port-25 features that matter for the mail server — any small VPS plan is plenty for MailWizz. Affiliate note: we may earn a commission if you sign up through this link, at no extra cost to you.
MailWizz talks to Postal over HTTPS (the API), so the app server needs no MX record, doesn't need port 25 open, and doesn't even need outbound SMTP — just normal outbound HTTPS. Keep it lean.
While you're in the DNS zone editor: reserve a second subdomain now for MailWizz's tracking domain (open/click tracking and unsubscribe links), e.g. track.yourdomain.com, pointed at this same app server. You'll wire it up in step 7.

3. Harden & prep the server

Copy the prep script to the new server and run it. It updates and hardens the box, then installs the full LEMP stack MailWizz needs.

# from your computer, upload the script:
scp mailwizz-prep.sh root@app.yourdomain.com:/root/

# then on the server:
ssh root@app.yourdomain.com
chmod +x mailwizz-prep.sh
sudo ./mailwizz-prep.sh

Run with no arguments and it asks for everything (domain, TLS email, DB name, PHP version). Or go non-interactive:

sudo ./mailwizz-prep.sh \
  --domain app.yourdomain.com \
  --email you@yourdomain.com

What the script does

  • Updates the system (and waits out the boot-time apt lock on fresh VPSes).
  • Sets the hostname; optionally sets a root password and moves the SSH port.
  • Firewall (ufw): allow SSH + 80/443, deny the rest.
  • fail2ban for SSH brute-force protection.
  • LEMP: Nginx, MariaDB, and PHP-FPM with every extension MailWizz needs (mysql, mbstring, curl, xml, zip, gd, imap, intl, bcmath, gmp, soap).
  • Tunes php.ini (memory, upload/post size, execution time) for MailWizz.
  • Creates the database + user and an Nginx vhost, then obtains a Let's Encrypt certificate.
Save the output. At the end the script prints the database name, user and generated password. Copy them — you'll type them into the MailWizz web installer in the next step.
PHP version. The script installs PHP 8.3 (from sury.org), which current MailWizz supports. If your MailWizz version requires a different version, pass e.g. --php 8.2.

4. Upload MailWizz & run the installer

Extract the CodeCanyon archive on your computer. Inside you'll find the application files (an index.php plus apps/, assets/, etc.). Upload them into the webroot so that /var/www/mailwizz/index.php exists:

# upload the extracted app (example with rsync):
rsync -az ./mailwizz/ root@app.yourdomain.com:/var/www/mailwizz/

# on the server, hand ownership to the web user:
chown -R www-data:www-data /var/www/mailwizz
MailWizz's installer checks that a handful of folders are writable (runtime, config and assets dirs). The chown above covers it. If the installer flags a path, make it writable and re-check.

Open https://app.yourdomain.com/install and follow the wizard:

  1. Requirements check — everything should be green (that's what the prep step set up).
  2. Database — enter the DB name / user / password the prep script printed; host 127.0.0.1.
  3. Admin account — create your backend login.
  4. Purchase code — paste your Envato Purchase Code.
Remove /install and lock down. After it finishes, delete or block the install folder (the installer reminds you), and never expose it again.

5. Cron jobs

MailWizz does its real work from cron — sending campaigns, processing bounces and feedback, running hourly/daily tasks. Without cron, nothing sends.

MailWizz shows you the exact lines for your install under Backend → Settings → Cron. They look like this (adjust the PHP path and webroot):

# crontab -e -u www-data — run as the web user so file permissions match
* * * * * /usr/bin/php8.3 /var/www/mailwizz/apps/console/console.php send-campaigns >/dev/null 2>&1
* * * * * /usr/bin/php8.3 /var/www/mailwizz/apps/console/console.php send-transactional-emails >/dev/null 2>&1
*/2 * * * * /usr/bin/php8.3 /var/www/mailwizz/apps/console/console.php bounce-handler >/dev/null 2>&1
*/4 * * * * /usr/bin/php8.3 /var/www/mailwizz/apps/console/console.php feedback-loop-handler >/dev/null 2>&1
*/10 * * * * /usr/bin/php8.3 /var/www/mailwizz/apps/console/console.php process-subscribers >/dev/null 2>&1
0 * * * * /usr/bin/php8.3 /var/www/mailwizz/apps/console/console.php hourly >/dev/null 2>&1
0 0 * * * /usr/bin/php8.3 /var/www/mailwizz/apps/console/console.php daily >/dev/null 2>&1
Verify. Send a test campaign to yourself. If it stays "sending" forever, cron isn't running — check crontab -l -u www-data and that the PHP path (/usr/bin/php8.3) is correct.
The bounce-handler line above is for the legacy IMAP-mailbox bounce path. With the Postal Web Api driver (step 7), bounces arrive over a webhook instead (step 8) and don't depend on this cron job — leave it in place anyway in case you ever add a different delivery server type that needs it.

6. General app configuration

A few things to set up before creating the delivery server in step 7.

Settings & cron key

Backend → Settings → Common — company/site name, support email, default timezone. This is also where you'll find the cron unique key used to build the exact cron lines from step 5 — copy it before setting up cron.

Tracking domain

MailWizz rewrites every link in a campaign for open/click tracking and appends unsubscribe links — these need their own hostname, separate from your sending domain.

  • DNS: add a CNAME (or A record) for the subdomain you reserved in step 2, e.g. track.yourdomain.com, pointing at this MailWizz app server.
  • In MailWizz: add track.yourdomain.com as a tracking domain (also selectable per delivery server — you'll pick it in step 7).
  • To confirm it's propagated, open the tracking domain URL directly in a browser — it should return the same page as your main MailWizz install.
This must be live and resolving before your first real campaign, or tracking/unsubscribe links will break.

Sending domains — you can skip this

MailWizz's own "Sending domains" feature exists only so MailWizz itself can generate a DKIM signature for a domain, for delivery servers that don't already sign outgoing mail on their own. Since Postal signs DKIM itself, this MailWizz feature isn't needed for the Postal Web Api driver — skip it entirely.

Smart Tracking

Backend → Settings (MailWizz 2.5.1+) — filters bot-generated opens/clicks (security scanners that auto-follow links before delivery) out of your campaign stats. It doesn't affect deliverability directly, but it matters for step 9 (warm-up): warm-up decisions lean on "watch opens & bounces before increasing," and that's only as good as the underlying open data. Turn it on before you start warming up.

Customer groups

Backend → Settings → Customer groups — only relevant if you're running this MailWizz install multi-tenant (reselling sending access to your own clients). Skip entirely if you're the only user.

7. Connect Postal as a delivery server

MailWizz ships a delivery-server driver made specifically for Postal — Postal Web Api. It talks to Postal over its HTTP API rather than SMTP, which is what makes the reputation-isolation story in the previous section true: MailWizz never becomes a visible SMTP hop.

Backend → Servers → Delivery servers → Create new → Postal Web Api.

FieldWhat to put
NameInternal label, e.g. "Postal - primary"
Hostname schemeHTTP or HTTPS — use HTTPS in production
HostnameYour Postal host, e.g. postal.yourdomain.com
Api keyThe API credential generated on the Postal side — see step 12
From emailA sender address on your verified sending domain
From nameDisplay name for that sender
Reply-To emailOptional reply-to override
Force FROM emailDefault Always — keeps every send on this server's fixed From address, for SPF/DKIM alignment
Force FROM name / Force Reply-ToDefault Never
Minute / Hourly / Daily / Monthly / Second quotaHard caps MailWizz enforces on this server; 0 = no cap. Set these for warm-up — see step 9
Pause after sendOptional delay (seconds) between sends — another throttle lever alongside quotas
Tracking domainThe domain you added in step 6

Save, then use MailWizz's "Send test email" on the delivery server — it should arrive, and you should see the message in the Postal dashboard.

Sending from more than one domain

A single delivery server = a single API key = one Postal Mail Server. If you send from multiple domains, Force FROM email decides how:

  • Always (default) — this server always sends as its own fixed From address. Use one delivery server per domain.
  • Never — the campaign's own From address is passed through, so one server can cover several domains — but only if those domains are all verified under the same Postal Mail Server this API key belongs to (see step 12).

Default recommendation: one Postal Mail Server / one MailWizz delivery server per domain, unless your domains are similar enough in volume and type that sharing one quota and warm-up curve is fine — Postal supports combining them, MailWizz doesn't require it.

Use for

Decides which mail type is allowed through this server — All, or a specific type (campaigns/lists vs. Transactional Emails). It's single-select, not a checklist.

If you send both marketing campaigns and transactional email (order confirmations, password resets) through the same Postal account, use two delivery servers instead of one — one set to Transactional Emails, one to All/Campaigns. That way a bulk-campaign problem (a quota hit, a complaint spike, warm-up throttling) can never delay a password-reset email, and vice versa.

Probability & rotation

Only matters with more than one active delivery server in the same "Use for" pool — irrelevant with a single server (leave at 100%).

Probability is a statistical weight, not a guarantee — 80% means roughly an 80% chance of being picked each time a server is chosen, not a guaranteed 80% share of volume. It also does nothing without its companion setting, "Change server at" (Backend → Settings → Cron, default 200 emails; overridable per customer group under Campaigns), which controls how many emails MailWizz sends before re-picking a server.

Two things worth knowing before you rely on this:

  • Probability weighting isn't always reliably respected in practice — verify actual distribution in your delivery logs after a real send rather than trusting the configured percentages.
  • Daily quotas break predictable rotation. Once a server hits its daily cap it gets excluded, and rotation among the rest can go effectively random rather than following your configured order. Since an active Warmup Plan (step 9) drives Daily quota automatically, warming up several servers in parallel while expecting a clean weighted split between them is a combination to test carefully, not assume works.

Customer & Locked

Only relevant for a multi-tenant MailWizz install (reselling sending access) — leave Customer blank and Locked = No for a single-tenant setup; the server is then account-wide and available to any send.

If you do assign + lock a server to one customer, that alone stops other customers from using it, but doesn't stop that customer from falling through to other system-wide servers once their dedicated server's quota is hit — for that you additionally need Customers → \[group\] → "Can send from system servers" = No.

Warmup plan

Built under Backend → Servers → Warmup Plans, then attached here — full detail in step 9. Once active, it can no longer be fully edited, so validate the numbers on a throwaway plan before attaching one to a real, in-use delivery server.

Domain policies

This is about the recipient's domain (gmail.com, yahoo.com…), not your sending domain — an Allow/Deny list used to dedicate a server to specific ISPs. MailWizz's own guidance leans toward doing this kind of routing at the MTA level (Postal) instead. More importantly: if a recipient's domain isn't covered by any available server's policy — including when the one server that does allow it has hit its quota — MailWizz doesn't retry indefinitely, it permanently marks those subscribers "Domain policy rejected." Combining Domain policies with tight quotas can silently kill delivery to a whole domain instead of just delaying it. Leave this empty unless you have a specific reason to dedicate a server to one ISP.

Additional headers

Custom headers injected into every message sent through this server. No required or recommended header for the Postal Web Api driver — leave empty unless you have a specific compliance or routing need.

8. Bounces, complaints & webhooks

With the Postal Web Api driver, bounce and complaint handling is webhook-based and largely automatic — no IMAP bounce mailbox to configure. MailWizz calls this mechanism DSWH (Delivery Servers Web Hooks).

  1. After saving the delivery server (step 7), the webhook URL to add on the Postal side is:
   https://your-mailwizz-install.com/index.php/dswh/postal
  1. Once Postal is sending events to that URL (step 12), MailWizz listens for MessageDeliveryFailed, MessageDelayed, MessageHeld and MessageBounced, and processes them automatically. You can leave Postal set to send every event type — MailWizz ignores anything it doesn't need.
  2. Hard bounces get marked and the subscriber moves to unsubscribed/blacklisted automatically — check under \[List\] → Subscribers, filter by status, after your first real send.
The single most common failure: if your Nginx/Caddy setup redirects HTTP → HTTPS site-wide, make sure the /dswh/ path is excluded from that redirect rule. A blanket redirect breaks the webhook silently — Postal's POST gets redirected instead of hitting MailWizz, and nothing processes.
Also watch for: webhook events arriving correctly with no bounce stats/unsubscribes showing up. There's no single fixed cause — treat the forced-bounce test in step 10 as a required check, not an assumption that it "just works" once the URL is in place.

No cron dependency for this path — contrast with the legacy bounce-handler cron line (step 5), which is only for IMAP-mailbox-based delivery server types.

9. Sending limits & IP warm-up

New IPs have no reputation. Send too much too soon and mailbox providers throttle or block you. Ramp up gradually — this is warm-up.

MailWizz has two built-in levers on every delivery server:

  • Quotas — Minute/Hourly/Daily/Monthly/Second, plus Pause after send (step 7). MailWizz simply stops sending through that server once a window's cap is hit.
  • Warmup plan — a reusable ramp schedule, created under Backend → Servers → Warmup Plans and attached on the delivery server form:
  • Sendings Count — number of ramp steps (max 720 hourly, 30 daily, 1 monthly)
  • Sending Limit — total volume across all steps
  • Sending Strategy — Exponential or Linear growth
  • Sending Increment Percentage — growth rate per step (Exponential only)
  • Sending Limit Type — Targeted (each step uses its own quota value) or Total (steps derived from the increment)
  • Sending Quota Type — Hourly, Daily or Monthly

Once active, an hourly MailWizz cron job auto-adjusts the server's quota to match the current ramp step — those quota fields stop being manually editable. An active Warmup Plan can't be fully edited afterward, so get the numbers right first.

Also set the mail server's own send/rate limit on the Postal side at or above what MailWizz is configured to send, so Postal is never the bottleneck (step 12).

A safe 4-week ramp (per sending IP)

Build a Warmup Plan with Sending Quota Type = Daily, Sending Limit Type = Targeted, ~28 steps, matched to this curve:

DaysDaily capNotes
1-3~50-100Most engaged subscribers only
4-7~250-500Watch opens & bounces before increasing
8-14~1,000-2,500Roughly double every few days
15-21~5,000-10,000Keep engagement high, complaints near zero
22-28~25,000+Scale to your normal volume
These are starting points — warm-up is driven by engagement, not a fixed table. If opens drop or complaints rise, slow down. Send your best, most engaged segments first — their opens and clicks build reputation fastest.
If Postal has several sending IPs/pools, create one MailWizz delivery server per IP, each with its own quota or warmup plan, and use "Use for"/Probability to spread the send across them. One caveat worth testing rather than assuming: Daily quotas from an active Warmup Plan are exactly the kind of thing that makes Probability-based rotation between servers unpredictable (see step 7) — verify actual per-server volume in the logs, don't just trust the configured percentages.

10. Test & go live

  1. Send test email — the delivery server's own "Send test email" button (step 7) first; confirm it arrives and shows up in the Postal dashboard.
  2. Seed test — send a small campaign to a mail-tester.com address and your own inboxes across providers (Gmail, Outlook, Yahoo).
  3. Check authentication — SPF, DKIM and DMARC should all pass (these come from your Postal setup; mail-tester shows them). No MailWizz "Sending domain" entry needed for this (step 6).
  4. Check tracking — click a tracking link and the unsubscribe link in the test send; confirm they resolve through the tracking domain instead of erroring.
  5. Force a bounce — send to a deliberately invalid address and confirm the webhook fires end-to-end and the subscriber actually flips to unsubscribed/blacklisted (the known trouble spot from step 8 — don't skip this).
  6. Watch both dashboards — Postal for delivery/bounces, MailWizz for opens, clicks and unsubscribes.
  7. Start small — begin with day-1 warm-up volume to your most engaged subscribers.
Aim for a clean first week: high opens, near-zero complaints, and hard bounces actually suppressed (verified in step 5, not assumed). That reputation is what carries every campaign after it.

11. Maintenance & security

  • Update MailWizz — when a new version ships, back up first, then use MailWizz's built-in updater (Backend → Update) or upload the new files.
  • Back up — the MySQL database and the /var/www/mailwizz files (especially apps/common/config). Automate a daily dump.
  • Keep the OS patchedapt update && apt upgrade regularly; the firewall and fail2ban from step 3 keep running.
  • Lock the backend — optionally restrict /backend to your IP in Nginx, and use a strong admin password + 2FA.
# /etc/cron.d/mailwizz-backup
30 3 * * * root mysqldump mailwizz | gzip > /root/mailwizz-$(date +\%F).sql.gz

12. Postal-side setup for MailWizz

Everything above assumes a few things are in place on the Postal side: a Mail Server to connect from, an API credential, a verified sending domain, and a bounce webhook pointed back at MailWizz. That's a distinct enough chunk of setup that it has its own guide — see the Postal Setup Guide for the full walkthrough (Mail Server/domain architecture, generating the API credential, verifying a domain, wiring up the bounce webhook, and send limits/IP pools).

Short version: one Postal Mail Server ≈ one MailWizz delivery server. Generate an API-type credential on that Mail Server for the Api key field in step 7, verify your sending domain(s) on it before your first test send, and add MailWizz's webhook URL (step 8) as a webhook endpoint on that same Mail Server so bounces flow back automatically.

13. Troubleshooting

SymptomWhat to check
Campaigns stay "sending" and nothing goes outCron isn't running or the PHP path is wrong. Check crontab -l -u www-data and that /usr/bin/php8.3 exists (which php8.3).
MailWizz test send fails immediately, nothing appears in Postal at allDomain not added/verified in Postal yet — see the Postal Setup Guide. Postal rejects messages for domains it doesn't know about outright.
Test email fails from the Postal Web Api delivery serverRe-check Hostname (right scheme, no typo) and the Api key — confirm it belongs to the right Mail Server and wasn't revoked/regenerated. Check whether the message appears (or errors) in Postal's dashboard.
Sends silently stop partway through a campaignA Minute/Hourly/Daily/Monthly quota was hit (step 7/9) — not an error, just a cap. Raise the quota or wait for the window to reset.
Tracking links / unsubscribe links broken or 404ingTracking domain (step 6) isn't resolving, or wasn't selected on the delivery server. Verify DNS, then re-check the field.
Emails land in spamUsually reputation, not config: confirm SPF/DKIM/DMARC pass (mail-tester), slow the warm-up (step 9), send to engaged subscribers, and make sure hard bounces are actually being suppressed (next row).
Bounces aren't being removed / subscribers don't flip to unsubscribedMost common cause: a site-wide HTTP→HTTPS redirect is catching /dswh/ and silently breaking the webhook — exclude it from the redirect rule. If that's not it, check Postal's webhook request log (Postal Setup Guide) to see whether it even reached MailWizz, then verify with the forced-bounce test (step 10) rather than assuming it works.
MailWizz shows a campaign as fully "sent" but real delivery lags behindPostal's own send limit (see the Postal Setup Guide) is lower than what MailWizz is configured to push — check Postal's message queue for a backlog and raise the limit to match.
Sending from one IP works, sending from a pool IP doesn'tConfirm the IP is both actually bound on the server and added in the IP Pool in the web UI (see the Postal Setup Guide) — listed-but-not-bound breaks that pool member silently.

Support

Need a hand? Contact us and we'll be glad to help.