| Audience | Marketers, lifecycle and CRM teams, data teams |
Native Delivery reports on sends in the app and writes every event back to your warehouse, so you can build your own audiences and reports on top of them.
Review a message's performance
The Engagement card on a message or campaign shows three metrics:
| Metric | What it counts |
|---|---|
| Sent | Messages handed off for delivery. |
| Opened | Share of messages opened, counted once per recipient. |
| Clicked | Share of messages with a link clicked, counted once per recipient. |
Opens and clicks here exclude machine and bot activity, including the prefetching that security scanners and mail privacy features perform. Each recipient counts once, so these measure how many people engaged, not how many times.
Three things mark activity as machine-generated: a known security-scanner signature, engagement within two seconds of the send, and the mail provider's own machine-open signal. That last one catches privacy features that load images automatically. Opens through Gmail's and Yahoo's image proxies are counted as real, because a person did open the message even though the request comes from the provider's servers.
These numbers are lower than the raw counts other platforms report, which usually include bot opens and count repeats. Compare your own trends over time instead of matching rates across platforms.
Engagement over time charts the same three metrics, and the Link clicks table breaks clicks down by URL with unique and total counts.
See who didn't receive a message
Recipients Hightouch withheld a message from are reported with a reason for each:
| Reason | What happened |
|---|---|
| Not subscribed to topic | Unsubscribed from the topic, or never confirmed an opt-in. |
| Channel unsubscribed | Opted out of all email. |
| Previously bounced | A previous email to this address bounced. |
| Suppressed | A previous spam complaint, or a suppression rule. |
| Invalid recipient | The address isn't a usable email address, so nothing was sent. Different from a bounce, where an address looked valid and delivery failed. |
| Quiet hours | The send fell in your quiet hours window. |
| Repeat send | Already received this message, and repeats aren't allowed. |
| User frequency limit | Already at their message limit for the period. |
| Volume cap | The message hit its total cap. |
| Channel daily cap | The workspace hit its daily limit for email. |
| Expired | A send with a Duration window didn't reach them inside it. |
These reasons explain a send that reached fewer people than expected. A high Not subscribed to topic count points at consent. A high Previously bounced count points at list quality.
A bounced address is suppressed permanently. There's no retry window. An address that bounced once won't be tried again.
Query the events in your warehouse
Hightouch writes five event tables into your warehouse, each suffixed with your workspace ID. Every row is one event tied to a recipient and a message.
| Table | What it records |
|---|---|
cep_dispatcher_events | What Hightouch attempted: sent, dropped, and failed. |
cep_deliverability_events | Delivery outcomes: delivered and bounced. |
cep_engagement_events | Opens, clicks with the clicked URL, and spam reports. |
cep_consent_events | Subscription changes by topic. |
Events land in your warehouse within about 15 minutes.
Managed models sit on top of these tables, already filtered and labeled, one per event type: Email delivered, Email opened, Email clicked, Email bounced, Email unsubscribed. Each is keyed on your parent model's primary key, so you can build an audience like "opened an email in the last 30 days" without writing SQL.
Query the raw tables directly for custom analysis, your own dashboards, or anything the managed models don't cover.
Table schemas
Every table carries the same identity and timestamp columns, listed with each table below. Use timestamp for time-based analysis, provider_event_type to filter to a specific event, and parent_model_pk to join back to your customer data.
Dispatcher events
What Hightouch attempted to send, written at send time. One row per recipient per send attempt.
| Column | Description |
|---|---|
id | Unique row identifier, for de-duplicating |
timestamp | When the event happened. Use this for time filters |
provider_event_type | The event, such as delivered, bounce, open, click |
channel_type | email or sms |
parent_model_pk | The recipient's primary key from your parent model |
user_handle | The recipient's email address or phone number |
message_id | The message this event belongs to |
message_execution_id | The specific send this event belongs to |
campaign_id | The campaign. Empty for journey sends |
campaign_run_id | The campaign run. Empty for journey sends |
message_name | The name of the message |
journey_id | The journey. Empty for messages sent from a campaign |
journey_name | The name of that journey |
subject | The rendered subject line this recipient received |
preheader | The rendered preview text |
from_email | The address the message was sent from |
from_name | The display name the message was sent from |
drop_reason | Why the recipient was skipped. Only set on dropped rows |
Deliverability events
What happened after handoff: whether the message reached the recipient's mail server.
| Column | Description |
|---|---|
reason | Why the message bounced or was dropped |
response | The receiving mail server's response |
bounce_classification | Bounce category, such as invalid address or reputation |
status | SMTP status code for the attempt |
attempt | Which retry attempt this was |
domain | Recipient domain, useful for per-provider analysis |
Engagement events
What recipients did with the message: opens and clicks.
| Column | Description |
|---|---|
url | The exact URL clicked. Empty on opens |
url_normalized | Same URL with tracking parameters stripped, better for grouping |
useragent | Mail client or browser that generated the open or click |
inferred_tz | Recipient timezone, where it could be determined |
Consent events
Every change to a recipient's subscription preferences. Append-only, which preserves the full history.
| Column | Description |
|---|---|
topic_id | The subscription topic. Empty means the change applied to the whole channel |
consent_status | The resulting state, subscribed or unsubscribed. Read this one |
updated_by | Who made the change |
updated_by_source | Where it came from, such as the preference page or a one-click unsubscribe |
FAQ
Why is a column missing from my table?
Hightouch creates a column the first time an event carries a value for it. A workspace that has never recorded a particular event won't have its columns yet, and they appear once the first matching event arrives.
Why do I see columns that aren't listed here?
These tables aren't a fixed schema. Additional detail reported during delivery is written as extra columns. Your tables may hold more than what's documented above.
Which timestamp should I use?
Use timestamp. The other timestamp columns record when the event moved through each stage of collection, and are mainly useful for diagnosing delays.
SMS reporting
SMS reports on sends and clicks. There's no open tracking for text messages.
| Metric | What it counts |
|---|---|
| Sent | Messages handed off for delivery |
| Delivered | Share that reached the recipient's handset |
| Clicked | Share where the link was clicked |
There's no Opened metric for SMS. Nothing in a text message reports that it was read, so clicks are your engagement signal. Click rate here is a share of messages sent.
The Deliverability page has an SMS tab with Sent, Delivered, Send failed, Clicked, Subscribed, and Unsubscribed.
A high Quiet hours (no timezone) count means your parent model is missing time zone data.
SMS writes to the same event tables. cep_engagement_events holds clicks, with short_id and user_agent on each row. STOP and START land in cep_consent_events alongside every other preference change.