Skip to main content

Email & SMS Campaigns (EDM)

OmniCRM's bulk-campaigns module (EDM — Electronic Direct Mail) lets operators build audience segments, design templates, and send bulk campaigns to customers over two channels: email (via SMTP) and SMS (via OmniMessage). A campaign targets one segment with one template on a chosen channel, with per-recipient personalisation, opt-out handling and tracking. This guide covers both channels.

See also: Mailjet <integrations_mailjet> for the transactional Mailjet integration, Notification API <integrations_notifications> for system notifications, Configuration and Customization <administration_configuration> for general config.

Scope note: This describes the bulk-campaign send path only (email and SMS). Transactional email (welcome, invoices, password resets, eSIM delivery, ticket updates) is delivered separately through the Mailjet REST API and is not affected by the settings below.

Overview

The module has these building blocks:

ConceptDescription
SegmentA targeted audience, defined by filter criteria (billing type, contact type, status, balance, …) or a static address list.
TemplateReusable content for a channel — an operator-uploaded HTML body (email) or a short SMS message, both with per-recipient personalisation.
CampaignA send: one template to one segment on a chosen channel, with per-recipient tracking.
ChannelHow a campaign is delivered — email (SMTP relay) or SMS (OmniMessage).

Channels

A campaign's channel decides everything downstream — delivery transport, template format, and which opt-out applies:

ChannelDelivered viaPersonalisationOpt-outDetails
EmailSMTP relay (Mailjet, Mailgun, SES, …)Jinja2 HTML templatesemail category prefs + unsubscribe + suppression listEmail delivery
SMSOmniMessagevar1var4 substitutionsms category prefs + OmniMessage STOPSMS delivery (OmniMessage)

Email templates and personalisation

Templates store raw HTML that you upload or paste into the template editor. Personalisation uses Jinja2 syntax, so templates can contain variables and simple logic:

  • {{ first_name }}, {{ last_name }}, {{ customer_name }}, {{ email }}
  • conditionals and loops, e.g. {% if first_name %}Hi {{ first_name }}{% else %}Hello{% endif %}

Any additional field provided by the segment for a recipient is also available to the template by name. Each recipient's email is rendered individually before sending.

Using bulk campaigns

This walkthrough covers building a segment, creating an email or SMS campaign, sending and tracking it, plus what recipients see. Everything lives under the Marketing section of the sidebar (Segments, Templates, Campaigns). (Operator-facing; written from the current implementation.)

Build a segment

A segment is the audience a campaign targets. From Marketing → Segments you can search, refresh and create segments. Each row shows the segment's criteria, current recipient count and when it was last refreshed; click a row to open it.

Segments list The Segments list. Recipient counts refresh on demand; clicking a row opens the segment.

Create Segment (or clicking a row to edit) opens the segment editor, where you define the audience from customer attributes — Customers without services, Zero balance customers, Billing Types, Contact Types and Customer Status. Save the segment first, then Generate Segment calculates the recipient list and Export CSV downloads it.

Segment editor with recipient count and CSV export The segment editor: criteria on the left, a live recipient count and CSV export at the bottom.

Segments are channel-agnostic — the same segment can be used for an email or an SMS campaign. Email campaigns target contacts with an email address; SMS campaigns target contacts with a phone number.

Upload a template

Email campaigns send an HTML template. From Marketing → Templates, create or edit a template and use Upload .html to load an HTML file straight into the body (or paste it). Templates support Jinja2 personalisation, and the Available Variables panel inserts {{customer_name}}, {{contact_firstname}}, {{contact_lastname}} and {{contact_email}} at the cursor.

Template editor — upload HTML and insert variables

SMS campaigns don't use a template — you write the message inline when composing the campaign.

Create a campaign

From Marketing → Campaigns → Create Campaign, a short wizard guides you through the campaign. The Channel toggle (Email / SMS) at the top of the wizard drives which steps and fields appear.

  1. Select Segment — choose the audience. The selected segment shows its recipient count.

    Campaign composer — select segment Step 1: pick the segment whose contacts will receive the campaign. The channel toggle sits above the steps.

  2. Template / Message — for an email campaign, choose an HTML template (click a template card to select it; Preview shows the rendered HTML). For an SMS campaign this step is skipped — the wizard shows a notice and you write the message in the next step.

    SMS campaign — template step skipped For SMS, the template step is replaced by a notice; you continue to write the message.

  3. Campaign Details — the channel-specific configuration:

    • Category — the communication category the campaign belongs to (loaded from edm.communication_categories); recipients opted out of this category for this channel are excluded.
    • Email: Campaign Name, Subject Line, From Email, From Name.
    • SMS: Campaign Name, Sender ID and the SMS Message (personalise with {{ first_name }}, {{ last_name }}, {{ customer_name }}).

    Email campaign details — channel and category Email campaign details: subject and sender fields, plus the category selector.

    SMS campaign details — sender ID and message SMS campaign details: the form switches to Sender ID and an SMS Message body.

  4. Review & Save — confirm channel, category, segment and content, then save as a draft. View recipients lists the segment's contacts (name, email or phone, customer) before you commit.

    Email campaign — review and save Review for an email campaign.

    SMS campaign — review with View recipients Review for an SMS campaign, with the View recipients link and the save-as-draft note.

The campaign is always saved as a draft first — you send it from the campaign detail page (below).

Campaigns list

Marketing → Campaigns lists all campaigns with their Channel (Email / SMS), status, recipient count and — once sent — open and click rates. Filter by All / Draft / Sent, and click any row to open the campaign.

Campaigns list with channel column The Campaigns list. The Channel column distinguishes email from SMS at a glance; sent campaigns show their open and click rates inline.

Send and track a campaign

Click a campaign to open its detail page:

  • A draft shows a Send Campaign button, enabled once the campaign has a segment and — for email, a template; for SMS, a message. If something is missing, the page lists what's required.
  • Once sent, the detail page shows:
    • Campaign Statisticsemail: delivered, open rate, click rate, opened/clicked/bounced; SMS: delivered / failed, with a Sync button that pulls the latest delivery counts from OmniMessage.
    • Recipients — a per-recipient list with status (sent, delivered, opened, clicked, bounced, failed). The address column shows the email address (email) or phone number (SMS).

Campaign detail — draft ready to send The campaign detail page for a draft SMS campaign, ready to send. Once sent, this page shows delivery/engagement stats and per-recipient status; for SMS, Sync refreshes counts from OmniMessage.

Once sent, the same page surfaces the Campaign Statistics panel and the per-recipient list:

Campaign detail — sent email campaign with statistics A sent email campaign: delivered count, open/click rate and opened/clicked/bounced totals, alongside the per-recipient list and each recipient's status.

Because tracking is provider-agnostic, Delivered is derived from engagement — an open or click proves the message arrived. Open rate is therefore measured against delivered (engaged) recipients rather than total sent.

What recipients see (unsubscribe)

Every email carries an unsubscribe link that opens a preference centre: the contact's email categories with their current opt-in state, a Save preferences action, and an Unsubscribe from all email option. A mail client's one-click unsubscribe opts the contact out of just that campaign's category.

Image files above live in docs/images/ (filenames prefixed edm-).

Email delivery

Campaign email is delivered over SMTP to a configured relay. This makes the send path provider-independent: the same OmniCRM configuration can point at Mailjet, Mailgun, Amazon SES, or a customer's own SMTP server.

For each recipient OmniCRM:

  1. Renders the template's HTML and text parts, and the subject, with the recipient's variables.
  2. Builds a multipart/alternative MIME message (plain-text fallback + HTML).
  3. Sends it over a single, reused SMTP connection for the whole campaign.
  4. Records a per-recipient result (sent when the relay accepts the message, failed on rejection).

Each message also carries a correlation header, X-MJ-CustomID, in the form campaign_{campaign_id}_recipient_{recipient_id}. Mailjet echoes this back in its event webhooks; other relays simply ignore the unknown header.

SMTP Relay Configuration

The relay is configured under the mailjet.smtp block in crm_config.yaml. Despite the section name, this is the generic SMTP relay used for all EDM campaigns and can point at any provider.

mailjet:
api_key: "your-api-key" # used by transactional email + as the default SMTP username
api_secret: "your-api-secret" # used by transactional email + as the default SMTP password
smtp:
host: in-v3.mailjet.com # SMTP relay hostname
port: 587 # submission port
use_tls: true # upgrade the connection with STARTTLS
use_auth: true # authenticate with username/password
username: '' # blank -> use mailjet api_key
password: '' # blank -> use mailjet api_secret
timeout: 30 # connection/command timeout in seconds

Parameters

ParameterTypeRequiredDefaultDescription
hostStringYesin-v3.mailjet.comHostname of the SMTP relay.
portIntegerNo587Submission port. Use 587 for STARTTLS, 465 for implicit TLS; many providers also offer 2525/25.
use_tlsBooleanNotrueWhen true, the connection is upgraded with STARTTLS after connecting. Set false only for a local test server.
use_authBooleanNotrueWhen true, OmniCRM authenticates with username/password. Set false only for an unauthenticated test server.
usernameStringNo(empty)SMTP username. When blank, OmniCRM falls back to the Mailjet api_key. For any non-Mailjet relay you must set this explicitly.
passwordStringNo(empty)SMTP password. When blank, OmniCRM falls back to the Mailjet api_secret. For any non-Mailjet relay you must set this explicitly.
timeoutIntegerNo30Connection and command timeout, in seconds.

Important: username/password default to the Mailjet API credentials purely for convenience. When pointing at a different provider, always set both explicitly — otherwise OmniCRM will try to authenticate to that provider with Mailjet credentials and fail.

Provider Setup Reference

The tables below give the SMTP connection settings and the native tracking mechanism for popular providers. Hostnames, ports, and credential conventions can change or vary by region/plan — confirm against your provider's current documentation. The Tracking notes are informational — what each relay offers in its own console if you use it; OmniCRM only sends SMTP and does not auto-detect or display provider-native stats.

Mailjet

SettingValue
Hostin-v3.mailjet.com
Ports587 (STARTTLS), 465 (TLS), 25 / 2525
UsernameAPI Key (Account → API Key Management)
PasswordSecret Key

Tracking: Open and click tracking are applied by Mailjet (account default, or per message via the X-Mailjet-TrackOpen / X-Mailjet-TrackClick headers). Engagement events are available in the Mailjet dashboard, via the Statistics API, and as Event API webhooks. OmniCRM already includes a Mailjet webhook handler; correlate events to recipients with the X-MJ-CustomID value OmniCRM sends.

Mailgun

SettingValue
Hostsmtp.mailgun.org (US) · smtp.eu.mailgun.org (EU)
Ports587 / 465 / 25 / 2525
Usernamepostmaster@your-sending-domain (or a custom SMTP login created on the domain)
PasswordThe SMTP credential password for that domain

Tracking: Enable open/click tracking per domain, or per message via X-Mailgun-Track, X-Mailgun-Track-Opens, X-Mailgun-Track-Clicks. Attach a campaign tag with X-Mailgun-Tag and custom correlation data with X-Mailgun-Variables (JSON). Stats appear in the Mailgun dashboard and Events/Stats API; engagement can be pushed to OmniCRM via Mailgun webhooks.

Amazon SES

SettingValue
Hostemail-smtp.<region>.amazonaws.com (e.g. email-smtp.us-east-1.amazonaws.com)
Ports587 / 2587 (STARTTLS), 465 / 2465 (TLS), 25
UsernameSES SMTP username (generated in the SES console)
PasswordSES SMTP password (distinct from your AWS access keys)

Tracking: Open and click tracking are enabled through a Configuration Set, attached per message with the X-SES-CONFIGURATION-SET header; add campaign dimensions with X-SES-MESSAGE-TAGS. Events (send, delivery, open, click, bounce, complaint) are delivered to an event destination (SNS, Kinesis Firehose, or EventBridge) and surfaced in CloudWatch — there is no built-in engagement dashboard, so plan an event destination if you want stats.

SendGrid (Twilio)

SettingValue
Hostsmtp.sendgrid.net
Ports587 / 465 / 25 / 2525
Usernamethe literal string apikey
Passworda SendGrid API key with Mail Send permission

Tracking: Open/click tracking is set in Tracking Settings (account) or per message via the X-SMTPAPI header (JSON filters such as opentrack/clicktrack, plus category and unique_args for correlation). Stats are in the SendGrid dashboard and Stats API; engagement reaches OmniCRM via the Event Webhook.

Postmark

SettingValue
Hostsmtp.postmarkapp.com
Ports587 / 2525 / 25
Usernameyour Server API Token
Passwordyour Server API Token (same value as username)

Tracking: Enable opens with X-PM-Track-Opens: true and links with X-PM-Track-Links: HtmlAndText. Tag campaigns with X-PM-Tag and add correlation with X-PM-Metadata-<key> headers. Use a Broadcast message stream for bulk/EDM sending. Stats are in the Postmark dashboard and Stats API; engagement reaches OmniCRM via webhooks.

Mailchimp Transactional (Mandrill)

SettingValue
Hostsmtp.mandrillapp.com
Ports587 / 465 / 25
Usernameyour Mailchimp/Mandrill account username (usually the account email)
Passwordany valid Mandrill API key

Tracking: Account default, or per message via X-MC-Track (e.g. opens,clicks_all). Tag with X-MC-Tags and add correlation with X-MC-Metadata (JSON). Stats are in the Mandrill dashboard and API; engagement reaches OmniCRM via webhooks.

Note: The standard Mailchimp Marketing product is not an SMTP relay. Only Mailchimp Transactional (Mandrill) offers SMTP sending.

Brevo (formerly Sendinblue)

SettingValue
Hostsmtp-relay.brevo.com
Ports587 / 465 / 25 / 2525
Usernameyour Brevo account login (email)
Passwordyour SMTP key (Brevo → SMTP & API → SMTP)

Tracking: Account-level open/click tracking; stats in the Brevo dashboard; engagement reaches OmniCRM via webhooks.

Generic / self-hosted SMTP

SettingValue
HostAs provided (e.g. a Postfix relay, or a customer's Microsoft 365 / Google Workspace relay)
PortsTypically 587 (STARTTLS) or 465 (TLS)
Username / PasswordAs provided by the operator

Tracking: A plain SMTP server provides no native engagement tracking. Use OmniCRM-native tracking (below) for opens and clicks, and bounce (DSN) processing for delivery failures.

SMS delivery (OmniMessage)

Bulk campaigns can also be SMS, delivered through the separate OmniMessage service. A campaign's channel is email (default) or sms; for SMS, set channel: sms and an sms_message.

Recipients are the segment's contacts that have a phone number, filtered by the campaign's category on the sms channel.

Sending follows the OmniMessage bulk-campaign flow (OmniCRM performs all three steps):

  1. POST /api/campaign_lists — upload recipients as a named list (msisdn + var1var4).
  2. POST /api/campaigns — create the campaign (message, list_id, source_msisdn, source_smsc, drip_tps).
  3. POST /api/campaigns/control {action: "start"} — start the drip.

Personalisation: OmniMessage templates support only {{ var1 }}{{ var4 }} (plus {{ msisdn }}). Write the familiar named placeholders and OmniCRM maps them automatically: first_namevar1, last_namevar2, customer_namevar3, emailvar4.

Status: OmniMessage has no delivery webhook — poll GET /api/campaigns/:id for its stats. POST /crm/email-campaign/campaigns/<id>/sms-sync pulls the latest stats into the campaign (total_delivered / total_bounced / total_sent); run it on a schedule for live numbers.

Config (smsc block in crm_config.yaml):

smsc:
campaigns_url: 'https://smsc:8443/api' # bulk-campaigns API base
source_msisdn: 'YourBrand' # default sender
source_smsc: 'IMS_SMSC' # SMSC route
drip_tps: 50 # per-campaign send rate
verify_tls: false # OmniMessage uses a self-signed cert
timeout: 30

OmniMessage maintains its own STOP/opt-out suppression; OmniCRM additionally applies the contact's sms category preferences when building the recipient list.

Statistics and Tracking

There are two independent ways to measure a campaign. They are not mutually exclusive.

What each model gives you

MetricRecorded at send (every relay)In your provider's own console ¹OmniCRM-native tracking (any relay)
Sent / queued
Send failures (rejected at relay)
Delivered✓ (proven by an open/click; otherwise assumed = sent − bounced)
Opens✓ (tracking pixel)
Clicks✓ (link redirect)
Bounces✓ (DSN processing)
Spam complaintsprovider feedback loop only
Unsubscribes✓ (unsubscribe link + List-Unsubscribe)

¹ OmniCRM does not detect which provider you use — it is configured with only an SMTP host and credentials, which look identical for every relay. The middle column is whatever your relay happens to offer, viewed in that provider's own console (not in OmniCRM) — unless you wire up its webhook (Model 1) or use OmniCRM-native tracking (Model 2).

Model 1 — Provider-native tracking

Most relays can apply their own open-pixel and link tracking and record the events. Because OmniCRM only holds an SMTP host + credentials, it cannot tell which provider that is, and it does not surface these numbers — you read them by logging into that provider's own console yourself. This needs no OmniCRM configuration; it is simply whatever your chosen relay offers.

To pull those events into OmniCRM's analytics instead, OmniCRM must ingest the provider's webhook and match each event back to a recipient using the per-message correlation field:

ProviderCorrelation field OmniCRM sets / uses
MailjetX-MJ-CustomID (handler already present)
MailgunX-Mailgun-Variables
Amazon SESX-SES-MESSAGE-TAGS
SendGridunique_args (via X-SMTPAPI)
PostmarkX-PM-Metadata-*
MandrillX-MC-Metadata

Each provider's event format differs, so ingesting a new provider's events requires a provider-specific webhook handler.

Model 2 — OmniCRM-native tracking (portable)

Because provider-native tracking only exists while you stay on that provider, OmniCRM measures engagement itself, identically on any relay. This is implemented and enabled by default:

  • Opens — a 1×1 tracking pixel is injected into the HTML; the request records the open against the recipient.
  • Clicks — links are rewritten to redirect through OmniCRM, which logs the click and forwards to the destination.
  • Delivered — an open or click is proof of delivery, so OmniCRM marks the recipient delivered on the first pixel/redirect hit — no provider webhook required. Recipients that were sent and never bounce are counted as assumed delivered.
  • Bounces — campaigns use a return-path that routes bounce notifications (DSNs) to OmniCRM's inbound mail server for processing.
  • Unsubscribes — a List-Unsubscribe header and an in-body unsubscribe link feed the existing unsubscribe suppression list.

With this model all statistics live in OmniCRM and survive a change of email provider.

Current support status

  • Active (default): SMTP campaign sending and OmniCRM-native tracking on any relaysent/failed at send, plus delivered, opened, clicked, bounced detected by OmniCRM itself (pixel, click-redirect, VERP bounce). No provider webhook required. See Tracking configuration.
  • Optional: if you happen to use Mailjet, its event webhook can also feed engagement in (correlated via X-MJ-CustomID) — but OmniCRM does not depend on it.

Tracking configuration

OmniCRM-native tracking is configured under the edm block in crm_config.yaml:

edm:
enable_open_tracking: true # inject the open pixel into campaign HTML
enable_click_tracking: true # rewrite links through the click redirect
enable_unsubscribe: true # add List-Unsubscribe header + in-body link
tracking_base_url: '' # public URL recipients can reach; falls back to crm.base_url
tracking_secret: '' # secret that signs tracking tokens; falls back to mailjet.api_secret
bounce_domain: '' # domain whose MX points at the inbound mail server (enables VERP bounces)
bounce_prefix: 'bounce' # VERP local-part prefix -> bounce+<token>@<bounce_domain>
ParameterTypeDefaultDescription
enable_open_trackingBooleantrueInject a 1×1 open-tracking pixel into the HTML body.
enable_click_trackingBooleantrueRewrite absolute http(s) links to redirect through OmniCRM so clicks are logged.
enable_unsubscribeBooleantrueAdd a List-Unsubscribe header (+ RFC 8058 one-click) and an in-body unsubscribe link.
tracking_base_urlString(crm.base_url)Public base URL for tracking links. Must be reachable by recipients' mail clients — not localhost. Falls back to crm.base_url when blank.
tracking_secretString(mailjet.api_secret)Secret used to HMAC-sign tracking tokens. Falls back to mailjet.api_secret when blank.
bounce_domainString(empty)Domain for VERP bounce addresses (bounce+<token>@<domain>). Its MX must point at OmniCRM's inbound mail server. Blank disables the VERP return-path.
bounce_prefixStringbounceLocal-part prefix for VERP bounce addresses.

Tokens are HMAC-signed (campaign_id, recipient_id) pairs, so tracking needs no extra database column and links cannot be forged or enumerated without the secret.

Note on tokens: the token is the campaign/recipient IDs signed, not encrypted — the IDs are base64-encoded, so a recipient who decodes a tracking URL can read the (sequential) campaign and recipient numbers. Tokens are therefore tamper-proof but not opaque. If the IDs must not be exposed, switch to random per-recipient tokens stored on EmailCampaignRecipient (adds a column) or an encrypted token payload.

The public tracking endpoints (no authentication) are served by the API:

EndpointPurpose
GET /crm/email-campaign/edm/open/<token>Records an open; returns a 1×1 transparent GIF.
GET /crm/email-campaign/edm/click/<token>?u=<url>Records a click, then 302-redirects to <url> (http/https only).
GET or POST /crm/email-campaign/edm/unsubscribe/<token>Adds the recipient to the suppression list and records a marketing/email opt-out in the contact's communication preferences (honoured on future sends).

Important: tracking_base_url must be a URL recipients can reach. Bounce capture additionally requires bounce_domain with an MX record pointing at OmniCRM's inbound mail server, which recognises VERP recipients and records the bounce instead of creating a ticket.

Communication preferences

Per-contact opt-outs are stored in a communication_preferences JSON field on the contact (no extra table), shaped as {category: {channel: opted_in}} — categories marketing, newsletter, product_updates, surveys; channels email, sms. It uses an opt-out model: a missing category/channel means the contact is opted in, so new categories or channels need no schema change.

A campaign unsubscribe records a marketing/email opt-out, and campaign recipient selection excludes any contact opted out of marketing/email (in addition to the global suppression list). EDM campaigns are treated as the marketing category. Note: the opt-out filter uses JSON_EXTRACT and is not indexed, which is fine at typical contact volumes.

Communication categories

Categories are operator-defined in crm_config.yaml under edm.communication_categories (no table), as a list of {key, name, channel, default_opt_in, active}:

edm:
communication_categories:
# Email marketing message types (opt-out: default_opt_in true = subscribed until they opt out)
- {key: marketing, name: Marketing, channel: email, default_opt_in: true}
- {key: newsletter, name: Newsletter, channel: email, default_opt_in: true}
- {key: promos, name: Promotions & Offers, channel: email, default_opt_in: true}
- {key: product_updates, name: Product Updates, channel: email, default_opt_in: true}
- {key: surveys, name: Surveys & Feedback, channel: email, default_opt_in: false}
- {key: events, name: Events & Webinars, channel: email, default_opt_in: false}
# SMS marketing message types (opt-in: default_opt_in false = excluded until they opt in)
- {key: marketing, name: Marketing SMS, channel: sms, default_opt_in: false}
- {key: promos, name: Promotions SMS, channel: sms, default_opt_in: false}
- {key: service_alerts, name: Service Alerts, channel: sms, default_opt_in: true}

Each category belongs to one channel (email or sms) and carries its own default opt-in, so an operator can run, say, an opt-out Promotions (email) category alongside an opt-in-required SMS Alerts (sms) category.

Effective opt-in for a contact resolves as: an explicit contact preference wins; otherwise the category's default_opt_in applies.

EndpointPurpose
GET /crm/email-campaign/communication-categoriesList the category catalog (read-only; defined in crm_config.yaml).
GET /crm/email-campaign/contacts/<id>/communication-preferencesResolved opt-in per active category for a contact.
PUT /crm/email-campaign/contacts/<id>/communication-preferencesSet one preference (body {category, channel, opted_in}).

Each campaign specifies a category (defaults to marketing). Recipient selection excludes contacts opted out of that category/channel, applying the category's default_opt_in for contacts with no explicit preference.

The unsubscribe link is a preference centre: it shows the contact's email categories with their current opt-in state (the campaign's own category highlighted), lets them save changes, and offers Unsubscribe from all email (a global suppression). A one-click unsubscribe (RFC 8058 List-Unsubscribe-Post) opts the contact out of just that campaign's category.

Deliverability

Regardless of provider, configure authentication for your sending domain so campaigns reach the inbox:

MechanismPurpose
SPFAuthorises the provider's servers to send for your domain.
DKIMCryptographically signs messages; each provider supplies the DKIM record(s) to publish.
DMARCAligns SPF/DKIM and sets a policy for failures; improves placement and reporting.
Return-PathSet so bounce notifications (DSNs) are routed where OmniCRM (or the provider) can process them.

Testing

For development and testing, point the relay at a local SMTP debug server that captures messages instead of delivering them:

mailjet:
smtp:
host: 127.0.0.1
port: 1025
use_tls: false
use_auth: false

A debug server is provided for this purpose; it prints each message's recipients, subject, and correlation ID (and, in verbose mode, the full rendered body), so you can confirm Jinja2 rendering and message structure without sending live email.

Troubleshooting

Campaign stuck or recipients marked failed immediately

  • Verify host/port and that username/password are set for the chosen provider (they default to Mailjet credentials when blank).
  • Confirm the sender address is a verified/authorised sender on the provider.
  • Check the provider's domain authentication (SPF/DKIM) is complete.

Email sends but personalisation shows blank or literal {{ ... }}

  • Confirm the variable name exists for the segment (e.g. first_name).
  • Check the template uses valid Jinja2 syntax; a template error fails that recipient and is recorded in its error message.

Provider dashboard shows opens/clicks but OmniCRM analytics do not

  • This is expected unless OmniCRM ingests that provider's webhook (Model 1) or OmniCRM-native tracking is enabled (Model 2). Mailjet event ingestion is available today; other providers require a provider-specific handler.

No open/click data anywhere

  • Open/click tracking must be enabled on the provider (account setting or per-message headers). On a plain/self-hosted relay there is no native tracking — use OmniCRM-native tracking.