Sending Emails

Sending emails is the core purpose of Omnivery. To facilitate sending, Omnivery provides multiple submission API interfaces as well as SMTP submission. You are free to choose the method of submission or even combine multiple methods based on your systems.

  • SMTP submission - recommended for legacy systems that do not support API submissions. It can be easily used for message relay from on-premises MTA to leverage the security, deliverability, and tracking features of Omnivery.

  • Mailgun API - an API mimicking the Mailgun API is the perfect choice for systems that have a Mailgun integration or programmers familiar with this API. This is our most complete API implementation, allowing for complete control and account management.

  • Sendgrid API - an API mimicking the Sendgrid API is the perfect choice for systems that have a Sendgrid integration or programmers familiar with this API. This API currently only supports sending messages and email validation.

  • Sparkpost API - an API mimicking the Sparkpost API is the perfect choice for systems that have a Sparkpost, aka MessageBird, aka Bird integration or programmers familiar with this API. This API currently supports sending messages, email validation, and domain management.

All of the submission methods listed above will give you access to the full suite of Omnivery features, given your sending domain is correctly set up.

Marketing or transactional?

Several of the headers below depend on whether your sending domain is set up as marketing or transactional. That is a property of the domain, not of the individual message. See marketing vs transactional.

Message Headers

Omnivery automatically adds a number of required headers to messages. In some cases, we will overwrite the supplied header for technical or compliance reasons. Headers shown in bold can be supplied during message submission.

Headers that take a yes/no value accept yes, true, 1 or on to enable, and no, false, 0 or off to disable, in any capitalisation. A value we do not recognise is ignored and your domain's configured default applies.

Header Description
X-OV-Callback-URL URL of a webhook endpoint to pass status information to
X-OV-Callback-Format Format of the webhook call. One of mailgun (the default), bloomreach, sendgrid or sparkpost. Has no effect without X-OV-Callback-URL; an unrecognised value is ignored
X-OV-Tag Message tag. Repeat the header, or comma-separate several tags in one
X-OV-Variables Message variables, as a JSON object. Shared by every recipient of the message. Invalid JSON is rejected
X-OV-Recipient-Variables Per-recipient variables, as a JSON object keyed by recipient address. Invalid JSON is rejected. See Merge Tags
X-OV-Track yes/no - enable/disable message tracking
X-OV-tracking_click yes/no - enable/disable click tracking. Only applies when tracking is enabled
X-OV-tracking_open yes/no - enable/disable open tracking. Only applies when tracking is enabled
X-OV-Require-TLS yes/no - enforce TLS for message delivery. If the receiving server offers no TLS, delivery fails rather than falling back to an unencrypted connection
X-OV-testmode yes/no - enable/disable test mode. A message in test mode is accepted but not delivered: it is logged as accepted and then as suppressed, with the reason Delivery suppressed: test-mode. Test messages count toward a separate test-mode limit, not your domain's sending limit - see SMTP submission. X-Mailgun-Drop-Message: yes has the same effect
X-OV-Template Template filename to be used for templating. Ignored on a submission that already renders inline - see How your content is rendered
X-OV-Template-Variables Template-specific variables, as a JSON object. Invalid JSON is rejected
X-OV-Substitutions SendGrid-style substitution tokens, as a JSON object. Shared by every recipient. Applied only to a SendGrid submission (see below). Invalid JSON is rejected
X-OV-Recipient-Substitutions Per-recipient substitution tokens, as a JSON object keyed by recipient address. Applied only to a SendGrid submission. Invalid JSON is rejected
X-OV-Deliver-By Schedule the message for delivery at an RFC-2822 date, up to 3 days ahead. See SMTP submission
X-Campaign-ID Campaign identifier (for reporting). X-Job-ID is used as a fallback
Message-Category The category of your email campaign, from a closed set of values. Defaults to bulk/newsletter on a marketing domain and transaction/commercial on a transactional one; a value outside your domain's set is replaced with that default - see marketing vs transactional
List-Unsubscribe Set automatically on marketing domains. If you supply this header, we replace it with our unsubscribe URL and redirect users to the URL you provided in case of GET requests. This way, all messages sent are always compliant with the bulk sender guidelines of all major mailbox providers, unsubscribes are always honored, and the user experience is retained
List-Unsubscribe-Post Omnivery supports one-click unsubscribe according to RFC8058, and this header is set accordingly to List-Unsubscribe=One-Click
List-Help Set automatically on transactional domains, when no List-Unsubscribe was supplied
Precedence Always set to bulk on marketing domains; a supplied value is replaced. Always removed on transactional domains, including one you supplied
Date Added if absent. On a scheduled message it is set to the real send time, not the submission time
Message-ID Always set by Omnivery; a supplied value is replaced
Feedback-ID Set automatically. Used by Gmail Postmaster Tools to group your traffic
APR-Info Set automatically, carrying the campaign identifier
X-CSA-Complaints This header is set automatically to csa-complaints@eco.de
X-Complaints-To This header is set automatically to abuse@omnivery.com
Abuse-Reports-To This header is set automatically to abuse@omnivery.com
X-Report-Abuse This header is set automatically to abuse@omnivery.com
X-Auto-Response-Suppress This header is set automatically to AutoReply, OOF, RN, NRN
X-Mailer This header is set automatically
CFBL-Address Omnivery supports RFC9477 Feedback-Loop reports, and this header is set automatically to cfbl@omnivery.net; report=arf

The List-* headers apply to the email channel only; they are not added to SMS messages.

Headers covered by our DKIM signature

Our signature covers From, Date, To, Cc, Reply-To, Subject, Message-ID, Content-Type, Content-Disposition, Mime-Version, List-Unsubscribe, List-Unsubscribe-Post, List-Help, X-CSA-Complaints, CFBL-Address, Feedback-ID and APR-Info, plus our own X-OV-Ident.

The remaining headers above - including Message-Category, Precedence, X-Complaints-To, Abuse-Reports-To, X-Auto-Response-Suppress and X-Mailer - are not signed. If an intermediate system rewrites a signed header, the signature breaks; a rewrite of an unsigned one is harmless.

Headers accepted but ignored

These are accepted for compatibility with other providers and currently have no effect. Do not rely on them: X-OV-Dkim, X-OV-Skip-Verification, X-OV-Time-Zone-Localize, X-OV-Delivery-Time-Optimize-Period.

How your content is rendered

Exactly one rendering mechanism runs per message. Which one is decided by the submission
format, not by which headers you send, and a mechanism belonging to a different format is
ignored rather than applied.

You submitted via Renders with X-OV-Template
Sparkpost API SparkPost templating not used
Sendgrid API, or SMTP with X-SMTPAPI Substitution tokens not used
Mailgun API, ZAPapi, Omnivery API, plain SMTP Stored template, if you name one used
SMTP with X-MSYS-API (SparkPost) nothing inline - substitution_data is ignored, as it is by SparkPost used

So a SparkPost submission that also carries X-OV-Substitutions renders with SparkPost
templating and the substitution tokens are left untouched - the two are different vendors'
mechanisms and cannot both be correct on one message. SparkPost itself ignores
substitution_data over SMTP, so this matches what the vendors do.

Merge tags (%recipient.*%) are not part of this choice. They are our own vocabulary and
run on every message regardless of format.

If you supply X-OV-Template-Variables without naming a template in X-OV-Template, the
variables are rendered inline with handlebars-style {{name}} placeholders.

Merge Tags

Omnivery supports % enclosed recipient merge tags. These can be used in the message body, in the subject line, and in templates, to be replaced with recipient-specific data. For example, the following message code:

<strong>%recipient.first_name%</strong>

When combined with a submission variable first_name with a value of Jack, will result in:

<strong>Jack</strong>

The recipient. prefix is required, and merge tags are case-sensitive: %first_name% and %Recipient.first_name% are not merge tags and are delivered as written.

Where the values come from

Merge tags resolve against four sources. Where the same name appears in more than one, the later source in this list wins:

  1. X-OV-Variables - a flat JSON object, shared by every recipient of the message.
  2. X-OV-Template-Variables - a flat JSON object, shared by every recipient of the message.
  3. X-OV-Recipient-Variables - a JSON object keyed by recipient address, so one submission can carry different values for each recipient. This is the Mailgun-compatible form:
X-OV-Recipient-Variables: {"alice@example.com": {"first_name": "Alice"}, "bob@example.com": {"first_name": "Bob"}}
  1. Variables we add - rcpt, from, env_from, subject, message_id, campaign_name, date.year, date.month, date.day, date.hour, date.minute, and domain.name. These describe the message being sent and cannot be overridden: a variable of yours with one of these names is replaced by ours. Other keys you put inside date or domain are kept.

Between your own three sources, a value replaces the lower one whole, including a nested object - it is not merged key by key.

With that header, %recipient.first_name% renders Alice in Alice's copy and Bob in Bob's. Addresses are matched without regard to capitalisation.

If your submission is already per-recipient - which it is whenever you send through one of the APIs, since those expand each recipient into its own message - you may send X-OV-Recipient-Variables flat, without the address key:

X-OV-Recipient-Variables: {"first_name": "Alice"}

Both shapes are accepted. We tell them apart by whether the top-level keys look like addresses, so a flat object whose values are themselves objects still works.

This one resolved map is given to every rendering mechanism - merge tags, SparkPost templating and stored templates alike - so the same name resolves the same way whichever route you submit by.

Nested values

A merge tag may address nested data with dots, to any depth:

X-OV-Variables: {"address": {"city": "Prague"}}

%recipient.address.city%

When a value is missing

Two different things happen, and the distinction matters:

  • If the message carries neither variables header, merge tags are left in the message exactly as written.
  • If a variables header is present but does not define the name used, the tag is replaced with nothing. A misspelled tag therefore disappears rather than showing an error, so check your variable names.

A variable whose value is 0 or an empty string renders as that value; it is not treated as missing.

There are specific merge tags to place an unsubscribe URL into the message body. You can use %unsubscribe_url%, [unsubscribe_url], [unsubscribe], or [signout], in any capitalisation, to be replaced with the actual unsubscribe link specific to the recipient of the message.

Sample unsubscribe code:
<a href="[unsubscribe]">Unsubscribe</a>

These work whether or not tracking is enabled for the message.

Variables

Each of the submission APIs supports the use of variables. These can either be used to drive the message content or just to pass variables that will be returned via webhooks.

Variables supplied in X-OV-Variables are returned in the variables field of every webhook event for that message, so you can carry your own identifiers - an order number, a user id - through to your event handler without storing a mapping yourself.