Menu

Documentation

This guide covers installation, configuration, and day-to-day usage of MailPurse.

Base URL

All URLs in this documentation assume your application base URL. Ensure APP_URL is set correctly.

https://mailpurse.up.railway.app/

Requirements

Backend:

  • PHP 8.2+
  • Composer
  • MySQL (or compatible) database
  • Redis (recommended for queues)

Frontend:

  • Node.js + npm

Optional (Bounce processing):

  • PHP IMAP extension (required for processing IMAP/POP3 bounce mailboxes)

Installation

There are two ways to install MailPurse:

  1. Installer wizard (recommended for most people, especially shared hosting)
  2. Manual installation (advanced / for VPS + SSH)

Video walkthrough

If the video does not load, open it here: https://www.youtube.com/watch?v=iX6pDQXXabk

Installer wizard (recommended)

Perfect for non-coders

You upload the files, open your site, and the installer will guide you through: server checks, app name + URL, database setup, migrations, and creating your first admin account.

Step-by-step

  1. In your hosting file manager (cPanel), upload the provided zip file to your domain folder (example: public_html).
  2. Extract the zip.
  3. Make sure these folders are writable: storage/ and bootstrap/cache/.
  4. Visit your domain in the browser (example: https://yourdomain.com). You will be redirected to /install.
  5. Follow the wizard and enter:
    • App name + App URL
    • Database host, name, username, password (from your hosting panel)
    • Admin email + password (this becomes your first admin login)
  6. Click Install and wait. The installer will run database migrations and finalize setup.
  7. When finished, click Login and sign in using the admin email/password you created.

Important note

There are no default admin credentials. Your admin account is created during the installer.

Manual installation (advanced)

When should I use manual install?

Use this if you have a VPS/server with SSH access and you’re comfortable running commands. If you’re on shared hosting without SSH, use the Installer wizard.

Where do I put the files?

Put the full project folder on your server (example: /var/www/mailpurse).

Your web server should point to the public directory inside the project (example: /var/www/mailpurse/public).

Typical command sequence (SSH)

composer install --no-dev --optimize-autoloader
npm install
npm run build

cp .env.example .env
php artisan key:generate

php artisan migrate --force
php artisan storage:link
php artisan optimize:clear

After this, visit your site and create your first admin account via the app.

Local MySQL setup

MailPurse supports any Laravel-compatible MySQL database. For local development, MySQL 8+ is recommended.

1) Ensure MySQL is running (choose one)

Docker (example)

docker run --name mailpurse-mysql \
  -e MYSQL_ROOT_PASSWORD=root \
  -e MYSQL_DATABASE=mailpurse \
  -p 3306:3306 \
  -d mysql:8.0

Native MySQL (Homebrew on macOS)

brew install mysql
brew services start mysql

2) Create the database (if you did not create it already)

CREATE DATABASE mailpurse;

3) Configure .env for MySQL

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=mailpurse
DB_USERNAME=root
DB_PASSWORD=
  • If you are using Docker with the command above, DB_PASSWORD for root is root.
  • Prefer 127.0.0.1 over localhost to avoid socket/TCP differences on some systems.
  • Ensure your PHP has the MySQL driver enabled (pdo_mysql).

4) Clear cached config and run migrations

php artisan optimize:clear
php artisan migrate

Install on cPanel

This documentation provides a step-by-step guide on how to deploy a Laravel application to cPanel shared hosting.

1. Upload and Extract Files

  • Upload: Log in to cPanel, open File Manager, and navigate to the public_html directory. Upload your project's .zip file here.
  • Unzip: Right-click the uploaded zip file and select Extract.
  • Move to Root: If your files extracted into a folder (e.g., /public_html/my-project), enter that folder:
    • Click Select All.
    • Click Move from the top toolbar.
    • Change the destination path to /public_html/ and click Move Files.

2. Configure Public Assets

To make the site accessible without typing /public in the URL, move the core entry files:

  • Go inside the public folder.
  • Select the index.php file and the build folder (if using Vite/React/Vue).
  • Move them directly into the /public_html/ directory.

3. Update index.php Paths

Since index.php is now in the same directory as the vendor and bootstrap folders, you must update the internal links. Open public_html/index.php in the cPanel File Editor. Find the following lines (usually near the top) and remove the ../ from the paths:

Change this:

require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';

To this:

require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';

4. Environment Configuration (.env)

Locate the .env file in public_html. If it’s hidden, click Settings in the top right of File Manager and check Show Hidden Files (dotfiles). Edit the file with these settings:

  • Database: Enter your cPanel MySQL Database name, username, and password.
  • App Settings:
APP_NAME=YourProjectName
APP_URL=https://yourdomain.com
APP_ENV=production
APP_DEBUG=false

5. Routing Fix (.htaccess)

Because you moved index.php to the root, you must ensure the server handles URLs correctly. Create a file named .htaccess in public_html and paste the following:

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Send Requests To Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

6. Finalize via Terminal

Open the Terminal in cPanel. Navigate to your project folder (usually cd public_html) and run these commands in order to set up the database and optimize performance:

php artisan key:generate
php artisan migrate --force
php artisan storage:link
php artisan config:cache
php artisan route:cache

Install on cloud hosting (VPS)

Recommended for production. Use a VPS (Ubuntu/Debian/CentOS) with Nginx or Apache, PHP-FPM, and a managed database.

Where you run the commands on a VPS

You normally connect to your server using SSH (for example via a tool like Terminal on Mac, or PuTTY on Windows). Once connected, you run the commands inside the project folder on the server (example: /var/www/mailpurse).

If you prefer, you can also build the frontend assets on your computer and upload only public/build, but Composer dependencies (vendor) are usually installed directly on the server.

Beginner SSH example (VPS)

ssh your-user@your-server-ip
cd /var/www/mailpurse
composer install --no-dev --optimize-autoloader
npm install
npm run build
  1. Install system requirements: PHP 8.2+, extensions, Composer, Node (optional if you build assets locally), and a database.
  2. Deploy the project (Git clone or upload) to a directory like /var/www/mailpurse.
  3. Configure your web server document root to /var/www/mailpurse/public.
  4. Create .env and set APP_ENV=production, APP_DEBUG=false, APP_URL, database and mail settings.
  5. Install dependencies and build assets:
    composer install --no-dev --optimize-autoloader
    npm install
    npm run build
  6. Run the application setup:
    php artisan key:generate
    php artisan migrate --force
    php artisan storage:link
    php artisan config:cache
    php artisan route:cache
  7. Run a queue worker with a process manager (Supervisor/systemd). Example Supervisor command:
    php /var/www/mailpurse/artisan queue:work --sleep=3 --tries=3 --timeout=120
  8. Configure cron to run Laravel scheduler every minute (see Scheduler / Cron section).
  9. Enable HTTPS (Let’s Encrypt) and confirm APP_URL uses https.

Install using Docker

This repository includes a Dockerfile, but does not include a full compose setup by default. You can run the container behind a reverse proxy and connect it to a database (recommended: MySQL) and Redis.

Where you run Docker commands

You run docker commands on the machine that has Docker installed (your computer, or a Docker-enabled server). The docker build command is run in the project folder (the folder that contains the Dockerfile).

Commands like php artisan migrate are run inside the container using docker exec.

Common beginner mistake

If you run docker build and get “Dockerfile not found”, you are in the wrong folder. Make sure you are in the project folder first.

  1. Build the image:
    docker build -t mailpurse:latest .
  2. Start the container with environment variables (example):
    docker run -d --name mailpurse \
      -p 8000:8000 \
      -e APP_ENV=production \
      -e APP_DEBUG=false \
      -e APP_URL=https://your-domain.com \
      -e PORT=8000 \
      -e DB_CONNECTION=mysql \
      -e DB_HOST=your-db-host \
      -e DB_DATABASE=mailpurse \
      -e DB_USERNAME=mailpurse \
      -e DB_PASSWORD=secret \
      mailpurse:latest
  3. Run migrations inside the container:
    docker exec -it mailpurse php artisan migrate --force
  4. Run queue workers and scheduler (recommended as separate processes/containers):
    # Queue worker
    docker exec -d mailpurse php artisan queue:work --sleep=3 --tries=3 --timeout=120
    
    # Scheduler (every minute) - typically via host cron
    docker exec mailpurse php artisan schedule:run
  5. Put the container behind a reverse proxy (Nginx/Traefik) and configure TLS.

Tip

Persist uploads by mounting storage as a volume, and consider using S3-compatible storage for production.

Google Sheets & Drive integrations

MailPurse supports connecting Google accounts to enable Google Sheets and Google Drive features (for example: importing subscribers from spreadsheets, exporting results, and using Drive-backed assets).

Important concepts

  • Connections are stored per customer account (each customer can connect their own Google account).
  • Customer access can be controlled per customer group: Admin → Customer Groups → Integrations → “Access to Google Integrations”.
  • Google login/register (the google_enabled setting) is separate from Sheets/Drive integrations.

Step 1: Create a Google Cloud project

  • Go to Google Cloud Console and create/select a project.
  • Under “APIs & Services”, enable the required APIs.

Enable these APIs

  • Google Sheets API
  • Google Drive API

Step 2: Configure OAuth consent screen

  • Go to “APIs & Services” → “OAuth consent screen”.
  • Choose External (recommended for SaaS) or Internal (Google Workspace only).
  • Set App name, support email, and add your domain (for production).

Step 3: Create OAuth Client ID

  • Go to “APIs & Services” → “Credentials” → “Create Credentials” → “OAuth client ID”.
  • Application type: Web application.

Authorized redirect URIs

Add both Sheets and Drive callbacks (adjust the base URL to your domain):

https://mailpurse.up.railway.app/customer/integrations/google/sheets/callback
https://mailpurse.up.railway.app/customer/integrations/google/drive/callback

Local development

Google requires an exact match for redirect_uri (scheme, host, port, and trailing slash). If you develop on http://127.0.0.1:8000 or http://localhost:8000, add redirect URIs for the exact host you use in the browser.

http://127.0.0.1:8000/customer/integrations/google/sheets/callback
http://127.0.0.1:8000/customer/integrations/google/drive/callback
http://localhost:8000/customer/integrations/google/sheets/callback
http://localhost:8000/customer/integrations/google/drive/callback

Step 4: Configure Google credentials in MailPurse

  • Go to Admin → Settings → Auth.
  • Set google_client_id and google_client_secret from the Google Cloud credentials screen.

APP_URL and caching

  • Ensure APP_URL matches your current host (for example: http://127.0.0.1:8000 vs http://localhost:8000).
  • If you changed settings, clear caches: php artisan optimize:clear.

Environment variables (optional)

You can also set them in your .env:

GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

Step 5: Enable access for customer groups

  • Go to Admin → Customer Groups → Edit.
  • Open the Integrations tab.
  • Enable “Access to Google Integrations”.

Step 6: Connect as a customer

  • Login as a customer.
  • Go to Customer → Integrations → Google.
  • Use “Connect” on either Google Sheets or Google Drive (each can be connected independently).

Scopes used

  • Sheets: https://www.googleapis.com/auth/spreadsheets
  • Drive: https://www.googleapis.com/auth/drive.file

Troubleshooting

  • redirect_uri_mismatch: the redirect URI in Google Cloud must exactly match the one shown above (including http/https and trailing slash).
  • invalid_client: verify Client ID/Secret and that you created a Web OAuth client.
  • “App not verified” warning: ensure consent screen is configured; for External apps you may need to add test users while in Testing mode.
  • If you changed settings, clear caches: php artisan optimize:clear.

Running background jobs (required)

Campaign sending and CSV imports run via queues. You must run a queue worker in production.

What is a “queue worker”?

A queue worker is a command that runs in the background and processes tasks that should not happen inside your browser request. For example: sending campaign emails, importing subscribers, and processing validations.

If the worker is not running, the app may look fine, but emails/imports will stay “stuck” and nothing will process.

  • php artisan queue:work (simple worker)
  • php artisan horizon (if Horizon is configured in your environment)

Beginner setup options

  • Local/dev: open a terminal in the project folder and run php artisan queue:work. Keep that terminal window open.
  • VPS/Cloud (recommended): run the worker using a process manager so it stays running after you log out (Supervisor or systemd).
  • cPanel/shared hosting: persistent workers are often not possible. If you cannot keep a worker running, use cPanel Cron Jobs to run php artisan queue:work --stop-when-empty every minute (less reliable than a VPS).
  • Docker: run the worker as a separate container or a separate process. Running it once with docker exec -d can stop on container restart.

How to quickly check if it’s working

  • Trigger an import or send a small test campaign and confirm the status changes within a minute.
  • On a server, check logs in storage/logs if jobs fail.

Scheduler / Cron (recommended)

The app schedules:

  • Starting scheduled campaigns every minute (campaigns:start-scheduled)
  • Processing bounces every 5 minutes (email:process-bounces --all)

Ensure your server runs Laravel scheduler:

What is “cron”?

Cron is a built-in server feature that runs a command on a schedule. We use it to run Laravel’s scheduler every minute. Laravel then decides which tasks should run (campaign checks, bounce processing, etc.).

Without cron, scheduled items may not run automatically.

* * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1

Where to add this cron line

  • VPS/Cloud: connect via SSH and add it to your server crontab (often using crontab -e). Make sure the path points to your real artisan file (example: /var/www/mailpurse/artisan).
  • cPanel: open “Cron Jobs” and paste the command there. Use the full path (example: /home/<cpanel_user>/mailpurse/artisan).
  • Docker: cron usually runs on the host machine, and you call the scheduler inside the container using docker exec.

Common setup mistakes

  • Using /path/to/artisan literally — replace it with your real path.
  • Cron runs, but the app can’t write to storage / bootstrap/cache due to permissions.

Email providers & environment variables

The application supports multiple delivery server types (SMTP and provider APIs). Provider credentials are read from environment variables.

Google OAuth (optional)

  • GOOGLE_CLIENT_ID
  • GOOGLE_CLIENT_SECRET
  • GOOGLE_REDIRECT_URI

Mailgun (optional)

  • MAILGUN_DOMAIN
  • MAILGUN_SECRET
  • MAILGUN_ENDPOINT (default: api.mailgun.net)

Amazon SES (optional)

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_DEFAULT_REGION (default: us-east-1)

SendGrid (optional)

  • SENDGRID_API_KEY

Postmark (optional)

  • POSTMARK_TOKEN

SparkPost (optional)

  • SPARKPOST_SECRET

Billing configuration (optional)

For non-coders, the easiest way to set up billing is from the Admin panel: AdminPayment Methods. You can enable a provider, choose Live or Sandbox, paste the keys, and choose the default provider.

Quick setup (no-code)

  1. Go to AdminPayment Methods.
  2. Enable the provider you want to use (Stripe / Razorpay / Flutterwave).
  3. Select Sandbox for testing, then switch to Live later.
  4. Paste the keys/secrets into the matching fields and click Save.
  5. Set the Default Provider dropdown to the provider you enabled.
  6. Make sure your APP_URL is correct and your site is reachable over HTTPS (required for webhooks in real deployments).

Stripe (recommended for subscriptions)

What you need

  • Stripe account
  • API keys: STRIPE_SECRET, STRIPE_PUBLIC_KEY
  • Webhook secret: STRIPE_WEBHOOK_SECRET

Webhook

Create a webhook endpoint in Stripe pointing to: POST /webhooks/stripe

Minimum events

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.payment_succeeded
  • invoice.payment_failed

Razorpay (Payment Links)

What you need

  • Razorpay account
  • API keys: RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET
  • Webhook secret: RAZORPAY_WEBHOOK_SECRET

Webhook

Create a Razorpay webhook pointing to: POST /webhooks/razorpay (event: payment_link.paid).

Customer return URL

After payment, Razorpay can send customers back to: GET /billing/razorpay/callback.

Flutterwave

What you need

  • Flutterwave account
  • API keys: FLUTTERWAVE_PUBLIC_KEY, FLUTTERWAVE_SECRET
  • (Optional) FLUTTERWAVE_ENCRYPTION_KEY
  • Webhook secret: FLUTTERWAVE_WEBHOOK_SECRET

Webhook

Create a Flutterwave webhook pointing to: POST /webhooks/flutterwave

Customer return URL

After payment, Flutterwave can redirect back to: GET /billing/flutterwave/callback

PayPal / Paystack

You can store PayPal/Paystack credentials in Payment Methods, but checkout/portal/webhook flows are not implemented yet. If you need PayPal or Paystack fully working, you will need a developer to implement the provider integration.

  • PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET
  • PAYSTACK_PUBLIC_KEY, PAYSTACK_SECRET

Advanced (env variables)

If you prefer environment variables, billing can also be controlled by BILLING_PROVIDER. Admin Payment Methods stores the same values in Settings.

Monetization

Monetization features help you sell access to the platform using plans and subscriptions, apply discounts with coupons, and track charges using invoices.

Before you start

  • Stripe, Razorpay, and Flutterwave are supported billing providers.
  • PayPal / Paystack appear in settings, but checkout/portal/webhooks are not implemented yet.
  • For billing to work reliably, your app must be reachable over the internet (for webhooks) and your APP_URL must be correct.
  • Invoices and subscription status updates depend on your provider configuration and webhooks.

Quick setup checklist (Stripe)

  1. Add Stripe keys: STRIPE_SECRET, STRIPE_PUBLIC_KEY, and STRIPE_WEBHOOK_SECRET.
  2. Create a Stripe webhook endpoint pointing to: https://your-domain.com/webhooks/stripe.
  3. In Stripe, enable events for the webhook (at minimum): customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.payment_succeeded, invoice.payment_failed.
  4. Create Stripe Products/Prices, then copy the Stripe IDs into your plan. Recommended: use a price_... ID.
  5. Create Plans in Admin, connect them to a Customer Group (limits/features), and set is_active.
  6. (Optional) Create Coupons in Admin. Coupons are synced to Stripe and become usable at checkout.
  7. Test checkout from a customer account and confirm the subscription becomes active or trialing.

Invoices

Invoices represent payments made by customers (for example: subscription renewals). They are typically generated automatically when a payment succeeds.

In the current implementation, invoices are read from Stripe (and/or from stored Stripe webhook events) in the Admin invoices area. Customers can also see recent billing history on their Billing page.

  • Invoice totals may include discounts (coupons) and taxes (VAT/Tax), depending on configuration
  • Keep your billing provider configured and webhooks reachable for accurate invoice status updates

If invoices are missing

  • Confirm Stripe keys are set and valid (STRIPE_SECRET is required).
  • Confirm the Stripe webhook endpoint is reachable from the internet and the webhook secret matches.
  • If you recently changed keys or webhook secret, update them and retry a test checkout.

Coupons

Coupons are discount rules that can be applied to a plan checkout or subscription.

Create coupons from the Admin area. When you save a coupon, the app attempts to sync it to Stripe and creates a Promotion Code. Customers can enter the coupon code during checkout.

  • Use coupons for promotions (percentage or fixed amount depending on your provider integration)
  • Consider setting limits (expiry date, maximum redemptions, and eligible plans)

Typical coupon settings (beginner-friendly)

  • Type: percent discount or fixed amount discount.
  • Duration: once, repeating (for N months), or forever.
  • Max redemptions: how many total uses are allowed.
  • Start/end dates: when the code becomes valid and when it expires.

If a coupon code is rejected at checkout

  • Ensure the coupon is active and not expired.
  • Ensure it has a Stripe Promotion Code (Stripe sync must succeed).
  • Coupon codes are stored uppercase; enter the same code shown in Admin.

Plans

Plans define what customers can purchase (features/limits and pricing). Customers subscribe to a plan to access the app.

Plans are tied to a Customer Group which defines limits (like monthly email quota) and permissions/features. In other words: Plans control pricing, and Customer Groups control access/limits.

  • Create plans in the Admin area, then assign/offer them to customers
  • Ensure plan pricing matches your billing provider products/prices, if applicable

Stripe IDs (important)

The plan can store Stripe identifiers: stripe_price_id (recommended) and/or stripe_product_id.

Best practice: create a recurring Price in Stripe (monthly/yearly), then paste the price_... ID into the plan. This ensures Stripe handles billing correctly and consistently.

If you only provide a product ID (prod_...), checkout can still work, but you must ensure the local plan price/currency/billing cycle match what you expect to charge.

Payment method

A payment method is how a customer pays for their subscription (for example: a card via Stripe).

To choose what customers use for checkout, go to AdminPayment Methods and set the Default Provider. Customers then go to CustomerBilling and pick a plan.

Customers typically manage their card/payment method through the billing provider's customer portal. In Stripe, this is the Stripe Billing Portal.

  • Customers must have an active payment method to enable renewals
  • If renewals fail, verify payment method status and your billing provider webhooks

Testing checklist (beginner-friendly)

  1. Create at least one active Plan in Admin.
  2. Enable one billing provider and set it as the default provider.
  3. Log in as a customer and go to the Billing page.
  4. Start checkout and complete a test payment (sandbox/test mode).
  5. Confirm the subscription status becomes active or trialing.

If renewals fail

  • Ask the customer to update their card in the billing portal.
  • Check the subscription status (e.g. past_due) and confirm Stripe webhooks are being received.
  • Confirm your server time and SSL are correct; webhooks often require HTTPS in real deployments.

VAT/Tax

If you charge VAT/Tax, confirm your billing provider tax settings and ensure invoices display the correct tax amounts.

  • Taxes may depend on customer billing country/state and tax ID (if collected)
  • Always verify tax rules with your accountant or local regulations

Practical notes

  • Stripe checkout can collect billing address; taxes are typically based on the address details.
  • If you require VAT IDs, make sure your process collects and stores them for your customers.
  • Test with a few different billing addresses to confirm the expected result before going live.

Servers

Servers are integrations used to send emails (Delivery Servers) and to receive/parse bounce mailbox messages (Bounce Servers).

Delivery servers

A delivery server is the outbound channel used to send campaigns and autoresponders. You typically configure one of:

  • SMTP credentials (host, port, username/password, encryption)
  • Provider API credentials (e.g. Mailgun/SES/SendGrid/Postmark/SparkPost)

Campaigns require selecting a delivery server. Make sure the server is verified/working before sending to real subscribers.

Operational notes

  • Sending runs through queues; ensure a worker is running (see Background jobs).
  • For best deliverability, set up DNS (SPF/DKIM) according to your provider.

Bounce servers

A bounce server is an IMAP/POP3 mailbox that receives delivery failure notifications and complaint messages. The app periodically connects to this mailbox, parses messages, and marks subscribers as bounced/complained.

  • IMAP/POP3 host, port, username/password, encryption
  • Folder selection (if your provider stores bounces in a specific folder)

Requirements

Bounce processing requires the PHP IMAP extension, and the scheduler must run regularly (see Scheduler / Cron).

If you also use provider webhooks for bounce/complaint events, see Webhooks.

Webhooks (bounces/complaints/tracking)

If you use provider webhooks for bounces/opens/clicks, configure your provider to call these endpoints:

  • POST /webhooks/mailgun (also /webhooks/mailgun/bounce, /webhooks/mailgun/open, /webhooks/mailgun/click)
  • POST /webhooks/ses (also /webhooks/ses/bounce, /webhooks/ses/open, /webhooks/ses/click)
  • POST /webhooks/sendgrid (also /webhooks/sendgrid/bounce, /webhooks/sendgrid/open, /webhooks/sendgrid/click)

Make sure APP_URL is set correctly so webhook URLs and tracking URLs are generated properly.

Using the app

Admin area

  • Manage customers, groups, and plans
  • Configure system settings
  • Manage delivery servers, bounce servers, sending domains, tracking domains

Customer area

  • Create email lists and add subscribers (manual or CSV import)
  • Create templates
  • Create campaigns and choose an email list + delivery server
  • Track results via analytics

CSV import notes

Subscriber CSV imports are queued. Ensure a queue worker is running. Uploaded CSV files are stored under storage/app/imports.

Marketing

Marketing features are available in the Customer area and are centered around a simple flow: build an audience (email list) -> verify addresses (validation) -> send messages (campaigns) -> automate follow-ups (autoresponders).

Operational requirements

  • Queue worker is required for imports, validations, and sending
  • Cron/scheduler is recommended for starting scheduled campaigns and automation
  • Use a verified delivery server and (recommended) tracking domain for best deliverability

Email lists

Email lists are your audiences. Campaigns and autoresponders target a list (and optionally segments, if enabled). Each list contains subscribers with fields like email, name, status, and tags.

How to build a list

  • Manual add subscribers in the list Subscribers area
  • CSV import (recommended for large lists). Imports are queued and stored under storage/app/imports
  • Subscription forms to collect signups (double opt-in is recommended for deliverability)

Best practices

  • Keep lists permission-based (only email people who opted in)
  • Regularly remove bounces/complaints and unsubscribe requests
  • Validate imported lists before sending high-volume campaigns

Campaigns

Campaigns send a message to subscribers in an email list using a delivery server. You can send immediately or schedule a start time.

Typical workflow

  1. Create or select an email list with confirmed/active subscribers
  2. Create a template (or write your content) and confirm links and tracking settings
  3. Create a campaign, select list + delivery server, and set subject/from details
  4. Send a test email, then send now or schedule

Important

Sending runs via background jobs. If a queue worker is not running, campaigns will appear stuck and no emails will be delivered.

Validation

Email validation helps improve deliverability by identifying invalid, risky, or disposable addresses before you send. Validation runs in the background and results can be used to clean lists.

When to validate

  • After importing a large list
  • Before re-engagement or high-volume campaigns
  • Periodically for old lists (addresses decay over time)

Notes

  • Validation is queued; ensure a worker is running
  • Different tools/providers may return different confidence levels; use your own sending thresholds

Autoresponders

Autoresponders are automated sequences triggered by subscriber events (for example: when someone joins a list). They are ideal for welcome sequences, onboarding, and drip campaigns.

How they work

  • Create an autoresponder for a list and choose a trigger
  • Add steps (emails) with delays (for example: send immediately, then after 2 days)
  • When the trigger fires, the app schedules and sends each step via background jobs

Important

Autoresponders require queues (and usually the scheduler) to process runs reliably.

Troubleshooting

  • If campaigns do not send: verify a queue worker is running and the campaign has confirmed subscribers.
  • If scheduled campaigns do not start: verify cron is running schedule:run.
  • If bounce processing does not work: install PHP IMAP and ensure bounce servers are configured and active.
  • If webhooks do not arrive: verify your site is publicly accessible and provider settings are correct.