Skip to main content
Matik Help Center home page Help Center
Matik Blog Case Studies
  1. Matik
  2. Building Templates
  3. Dynamic Content

[BETA] Using Matik Analytics as a Data Source

Beta Feature: Matik Analytics is currently in beta. Contact your Technical Account Manager if you are interested in access.

Matik Analytics lets you query the usage and engagement data Matik already collects about your enterprise, the same way you query a data warehouse or a CRM. You write SQL against a small set of curated tables, and the results become Dynamic Content or Input options like any other data source.

There is nothing to connect. Matik Analytics is not in the list of data sources you add under Connect Data Source, and it needs no credentials, no host, and no permissions setup. Once it is enabled for your enterprise, it appears in the data source picker on its own.

Every query is automatically scoped to your own enterprise. You never write a filter to limit results to your company's data, and there is no way to see another company's.

Example Use Cases

Report on Matik usage in the format your team already uses

Instead of exporting a fixed CSV and reshaping it, query the data directly and get exactly the columns and grouping you need.

For example, to build a monthly adoption slide for standalone, non-email presentations, aggregate presentation activity by month and user group. A user can belong to more than one group, so presentation_counts stores group membership as a comma-joined list. Split that list into one row per group before grouping:

WITH group_rows AS (
    SELECT
        DATE_TRUNC('month', p.created_on) AS month,
        ids.id AS user_group_id,
        names.name AS user_group,
        p.total_presentations
    FROM presentation_counts p
    CROSS JOIN unnest(string_to_array(p.user_group_ids, ',')) WITH ORDINALITY AS ids(id, pos)
    JOIN unnest(string_to_array(p.user_groups, ',')) WITH ORDINALITY AS names(name, pos)
        ON names.pos = ids.pos
    WHERE p.created_on >= CURRENT_DATE - INTERVAL '6 months'
        AND p.is_attachment = FALSE
        AND p.template_source_type <> 'email'
)
SELECT
    month,
    user_group_id,
    MAX(user_group) AS user_group,
    SUM(total_presentations) AS presentations
FROM group_rows
GROUP BY month, user_group_id
ORDER BY month, presentations DESC

As with any other piece of Dynamic Content, the result can drive a table, a chart, or an Insights summary.

Follow up with the recipients who engaged

Matik Analytics allows you to leverage your own Matik Mail performance analytics to drive personalization. For example, after a campaign, you may want to send a targeted follow-up only to people who showed interest. A Matik Analytics query returns the recipients who clicked in the last 30 days:

SELECT DISTINCT recipient_email
FROM email_events
WHERE event_type = 'click'
    AND occurred_at >= CURRENT_DATE - INTERVAL '30 days'
    AND flow_name = &:flow_name

Reference that Dynamic Content inside a query on your own contacts data to build the follow-up list:

SELECT email
FROM contacts
WHERE email IN (
    SELECT recipient_email
    FROM {{recently_engaged_recipients}}
)

This works in both directions. A Matik Analytics query can be nested inside another data source's query, and another data source's Dynamic Content can be nested inside a Matik Analytics query.

Selecting Matik Analytics as a Data Source

Matik Analytics can be used to power Dynamic Content or Inputs, just like any other data source. Once the beta is enabled for your enterprise, select Matik Analytics as the data source.

Querying Matik Analytics

Matik Analytics is organized into Analytics Categories. Each category contains related tables that you can query.

Category Included Data Tables Requires
Email analytics Sends and engagement for emails your enterprise has sent through Matik. email_sends, email_events Matik Mail
Generation performance Timing for each step of the presentations your enterprise has generated. generation_times, generation_times_daily None
Presentation activity Current users, presentation generations and counts, and Dynamic Content query timings, warnings, and errors. users, generations, presentation_counts, dc_run_times, dc_errors None

The dropdown only lists categories your enterprise can use.

A single query reads from one category, so you cannot join a table in Email analytics to a table in Presentation activity. To combine them, build separate Dynamic Content for each and reference one inside the other.

Email Analytics

Available to enterprises with Matik Mail enabled.

email_sends - one row per recipient of a sent email, with delivery state and engagement totals.

Column Type Description
send_id integer Stable ID for this recipient's email send. Join to email_events.send_id.
sent_at timestamp UTC time when the send record was created.
recipient_email text Email address the message was sent to.
recipient_type text Whether the recipient was on the to, cc, or bcc line.
template_name text Name of the email template used.
template_id integer ID of the email template used. Stays the same if the template is renamed. Null if unavailable, including on some older rows.
flow_name text Name of the scheduled flow that sent it, if any.
delivery_status text Delivery outcome for the message.
open_count integer Number of times this recipient opened the message.
click_count integer Number of link clicks by this recipient.
unsubscribed boolean Whether the recipient unsubscribed from this send.
spam_report boolean Whether the recipient reported the message as spam.
last_open_at timestamptz Most recent open in UTC time, or null if never opened.
last_click_at timestamptz Most recent click in UTC time, or null if never clicked.
inputs_json text Email input values as JSON object text. Cast to jsonb to query keys. {} means empty inputs; NULL means unavailable or invalid data, including on some older sends.

email_events - one row per email engagement event, either an open or a click.

Column Type Description
send_id integer ID of the recipient's email send. Join to email_sends.send_id. Null if unavailable, including on some older rows.
event_type text The kind of engagement, such as open or click.
occurred_at timestamptz UTC time when the event happened.
recipient_email text Email address that generated the event.
recipient_type text Whether the recipient was on the to, cc, or bcc line.
template_name text Name of the email template used.
template_id integer ID of the email template used. Stays the same if the template is renamed. Null if unavailable, including on some older rows.
flow_name text Name of the scheduled flow that sent it, if any.
url text Clicked URL. Null for opens.
attachment_id integer Id of the clicked attachment. Null for opens and non-attachment clicks.
attachment_name text Name of the clicked attachment. Null for opens and non-attachment clicks.
attachment_template_id integer Template ID of the clicked attachment, which may differ from the sending email's template_id. Null when there is none.
attachment_template_name text Template of the clicked attachment. Null when there is none.
Querying email inputs

inputs_json uses the same input names and values as the Mail CSV. For example, this query lists opened emails alongside an input value. Replace customer_name with one of your input names:

SELECT s.send_id, s.sent_at, s.recipient_email,
       s.inputs_json::jsonb ->> 'customer_name' AS customer_name
FROM email_sends s
WHERE s.inputs_json IS NOT NULL
  AND EXISTS (
    SELECT 1
    FROM email_events e
    WHERE e.send_id = s.send_id
      AND e.event_type = 'open'
  )
ORDER BY s.sent_at DESC
LIMIT 50;

Join sends and events on send_id. Several events can belong to one send, so use EXISTS or aggregate events before counting sends. The example above keeps one row per send, even after multiple opens.

Input and event send-ID coverage was enabled on October 14, 2026. Historical data was not fully backfilled, so some older rows still have NULL inputs_json or NULL send_id.

Generation Performance

How long each step of a presentation generation takes. Use generation_times_daily for reporting and generation_times when you need individual runs.

generation_times - one row per pipeline step of a presentation generation, with its elapsed time.

Column Type Description
occurred_on date Day the generation ran.
event_name text Pipeline step that reported this timing.
elapsed_ms bigint How long the step took, in milliseconds.

generation_times_daily - daily rollup of generation timings per pipeline step: run count, median, and 95th percentile.

Column Type Description
occurred_on date Day the runs happened.
event_name text Pipeline step that reported these timings.
runs integer Number of times the step ran that day.
p50_ms numeric Median elapsed milliseconds across the day's runs.
p95_ms numeric 95th percentile elapsed milliseconds across the day's runs.

Presentation Activity

presentation_counts - daily presentation generation counts. One row per UTC day, user, template, and whether the row counts rendered email attachments.

Column Type Description
created_on date UTC day the presentations were created. The current partial day is dropped, so days are only ever complete.
user_id integer Id of the user who created the presentations.
user_name text Name of the creating user, as of the row's last sync.
user_email text Email of the creating user.
user_group_ids text Comma-joined ids of the groups the user belongs to. The stable key for group filters.
user_groups text Comma-joined names of the groups the user belongs to, in the same order as user_group_ids. Display text, as of the row's last sync.
is_producer boolean Whether the user holds a non-consumer role. Matched by role name, so custom roles count.
template_id integer Id of the template the presentations were generated from.
template_name text Name of the template, as of the row's last sync.
template_source_type text Kind of template: powerpoint, google_slides, email, and so on. email marks email shell rows.
generation_source text

Where the generation was started:

  • web_app: The Matik web app
  • sfdc_app: The Matik App for Salesforce
  • mcp: An AI tool connected to Matik, such as Claude or ChatGPT
  • external_api: The Matik API
  • assistant: Matik's AI in-app
  • scheduled_flow: A flow that runs at a scheduled time
  • triggered_flow: A flow that runs when your data changes
  • NULL: Not recorded
is_attachment boolean Whether the row counts presentations rendered as email attachments.
total_presentations bigint Completed presentations created that day. Test runs and Mail Outbox previews are excluded, and deleted presentations still count.
total_failed bigint Presentations that failed that day through an error, a failed condition, or a timeout.

users - one row per current Matik account user, including inactive users and users with no presentation activity. Deleted accounts are hidden. Names, roles, groups, and status reflect the latest complete roster snapshot.

Column Type Description
user_id integer Stable user ID for joining to presentation_counts.user_id or generations.user_id.
email text Current account email address.
name text Current user name.
title text Current user title, if set.
provisioned_at timestamp UTC time when the account was created.
status text Current account status: active or inactive.
role_names text Current role names, comma-separated in role-ID order; empty if none.
group_ids text Current group IDs, comma-separated; use these for exact membership.
group_names text Current group names for display, joined with comma-space. A group name itself can contain a comma.
observed_at timestamp UTC time of the latest complete roster snapshot.

generations - one row per presentation generation. Includes today's generations, generations still in progress, failed generations, and deleted presentations.

Column Type Description
generation_id integer ID of the presentation. Stable, so you can use it to join to other data.
created_at timestamp UTC time the generation started (not when it finished).
user_id integer ID of the user who created it. Null if unavailable.
template_id integer ID of the template it was generated from. Null if unavailable.
status text done means it succeeded. error, failed condition, and timeout mean it failed. Other values mean it is still in progress.
generation_source text Where the generation was started. Same values as in presentation_counts.
presentation_type text Kind of presentation. email marks the email itself, as opposed to its attachments.
is_deleted boolean Whether the presentation was deleted.
bulk_presentation_id integer ID of the bulk generation run it belongs to, if any.
is_attachment boolean Whether the row is a presentation rendered as an email attachment.

dc_run_times - one row per data source query execution during a production presentation generation, including each loop iteration. The duration measures the data source call, not query preparation, formatting, rendering, or total generation time.

Column Type Description
execution_id text Stable ID of this query execution.
presentation_id integer Presentation generation ID; join to generations.generation_id.
template_id integer Template ID at generation time.
user_id integer ID of the user who created the presentation.
generation_attempt_id text Generation attempt ID, when available.
dc_id integer Stable Dynamic Content ID, including after a rename.
dc_version_id integer Version of the Dynamic Content that ran, if available.
dc_name text Dynamic Content name captured when the query ran.
dc_type text Dynamic Content type captured when the query ran.
data_source_type text Data source type used by this query.
loop_index integer Zero-based loop iteration; null when the content was not looped.
started_at timestamp UTC time when the data source query began.
finished_at timestamp UTC time when the data source query finished.
elapsed_ms numeric Time spent in the data source query, in milliseconds.
row_count integer Rows returned, excluding the header. Null for non-tabular results or when rows and columns were swapped.
outcome text Query result: success, error, or timeout.
succeeded boolean Whether the data source query succeeded.
is_bulk boolean Whether the query ran as part of a bulk generation.

Query timings are available from October 15, 2026 onward. Earlier timings were not backfilled. Cached content and content that does not make a separate data source call have no row. A timeout stopped by the generation time limit may include only the elapsed time observed before that limit.

Find slow Dynamic Content queries

This query ranks Dynamic Content by the 95th percentile of successful query durations in the last 30 days. It requires at least 10 queries per ID so a single slow query does not dominate the list:

SELECT dc_id, MAX(dc_name) AS dc_name,
       COUNT(*) AS queries,
       PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY elapsed_ms) AS p95_ms
FROM dc_run_times
WHERE started_at >= CURRENT_DATE - INTERVAL '30 days'
    AND outcome = 'success'
    AND dc_id IS NOT NULL
GROUP BY dc_id
HAVING COUNT(*) >= 10
ORDER BY p95_ms DESC
LIMIT 50;

dc_errors - one row per Dynamic Content warning or error during a production presentation generation. A generation can have several issues, and a warning does not mean the whole generation failed. The message is a short summary; the full details remain in the generation log.

Column Type Description
issue_id text Stable ID of this diagnostic event.
occurred_on timestamp UTC time when the warning or error occurred.
presentation_id integer Presentation generation ID; join to generations.generation_id when that generation is available.
generation_log_url text Link to the full generation log, subject to normal access controls.
template_id integer Stable template ID, if available.
template_name text Template name captured for this row, if available. Older recovered events may use a later name.
user_id integer ID of the user who generated the presentation, if available.
user_email text Generating user email captured for this row, if available. Older recovered events may use a later email.
dc_id integer Stable Dynamic Content ID. Null if the issue could not be attributed to an ID.
dc_name text Dynamic Content name recorded in the generation log.
dc_type text Dynamic Content type recorded by the generation, when available.
data_source_type text Data source type recorded by the generation, when available.
attribution_status text How the Dynamic Content ID was found: explicit_id, run_local_id, or unresolved.
issue_level text Log severity: error or warning. A query failure can appear as a warning.
error_category text Diagnostic type, useful for grouping similar issues.
message text Short summary. Use generation_log_url for full details if you have access.
is_bulk boolean Whether the issue happened during a bulk generation.

Recent diagnostics are available, and some older events have been recovered from generation logs. Historical coverage may be incomplete. Use dc_id to group across name changes. A null dc_id means the issue could not be reliably attributed to a Dynamic Content ID.

Find Dynamic Content with warnings or errors

Count both issue events and affected generations. One generation can have several issues:

SELECT dc_id, MAX(dc_name) AS dc_name,
       COUNT(*) AS issue_events,
       COUNT(DISTINCT presentation_id) AS affected_generations
FROM dc_errors
WHERE occurred_on >= CURRENT_DATE - INTERVAL '30 days'
    AND dc_id IS NOT NULL
GROUP BY dc_id
ORDER BY affected_generations DESC, issue_events DESC
LIMIT 50;

Things to note:

  • An email send creates a shell row (template_source_type = 'email') plus a row for each rendered attachment. Filter on is_attachment or template_source_type so sends are not counted twice.
  • generation_source data started getting tracked in September 2026, so generations from before then may show NULL.
  • generations starts on October 9, 2026 at 21:36 UTC. Earlier presentations are not included. Use presentation_counts for history before that date.
  • generations and presentation_counts count different sets of rows. presentation_counts leaves out the current day, in-progress generations, and rows with no creator or template. Use the same filters on both before comparing totals.

Writing Queries

Matik Analytics accepts one read-only PostgreSQL query at a time. Start your query with SELECT or WITH ... SELECT, and use only the tables listed for the selected Analytics Category.

Matik Analytics supports referencing &:inputs and {{nested_dc}}, just like any other SQL query in Matik. These references can be used anywhere their resolved values are valid, including using nested table Dynamic Content in a FROM or JOIN clause. Matik Analytics Dynamic Content can also be nested in another SQL data source that supports table Dynamic Content.

Supported SQL

  • Joins between tables in the same Analytics Category
  • Subqueries and common table expressions (CTEs), including recursive CTEs
  • Aggregates and window functions
  • Set operations such as UNION, INTERSECT, and EXCEPT
  • Standard PostgreSQL expressions and functions, except for the restricted operations described below

SQL that is not supported

  • More than one SQL statement in a query. A single trailing semicolon is allowed.
  • Statements that write data or modify the database, including INSERT, UPDATE, DELETE, MERGE, CREATE, DROP, ALTER, and TRUNCATE
  • Database administration or session commands, including GRANT, REVOKE, COPY, SET, EXPLAIN, SHOW, and VACUUM
  • Locking or table-creation forms such as FOR UPDATE and SELECT INTO
  • Schema-qualified table names, such as public.email_sends
  • System catalogs, raw Matik application tables, or tables from another Analytics Category
  • A CTE that uses the same name as one of the category's built-in tables
  • Functions that inspect or modify the database server or session, access server files, open external database connections, or pause or control database processes

Matik validates each query before it runs and displays an error when the query uses unsupported SQL.

You can also ask the Matik AI Agent to write or update an analytics query for you, rather than authoring the SQL yourself.

Limitations

  • Data freshness varies by table. Most analytics data syncs throughout the day and is typically less than two hours behind the activity it describes. dc_run_times and dc_errors are recorded as generation telemetry is received; some diagnostics recovered from generation logs may appear later. In presentation_counts, the current partial day is always excluded, so the most recent day you can query is the last complete UTC day. generations includes the current day, up to the latest sync. users.observed_at shows when the current roster was last refreshed, not when an individual account was edited.
  • One statement per query. A second statement after a semicolon is rejected.
  • One category per query. Tables from different categories cannot be joined in a single query.
  • Query text is limited to 1 MB.
  • Query timeout. Matik Analytics queries that run longer than 60 seconds are stopped.
  • Saved results. A saved query result holds up to 50,000 rows.
  • Result size. A saved query result can hold up to 16 MB of data, and a test run in the query editor up to 256 KB. A query that returns more fails with "This query returned more data than Matik Analytics allows. Select fewer rows or narrower columns."

Best Practices

  • Filter out attachment rows when counting unique sends. In presentation_counts and generations, an email send produces both an email row and attachment rows. Without a filter on is_attachment together with template_source_type (in presentation_counts) or presentation_type (in generations), the query will count both the email and any attachments.
  • Filter and join on template IDs, not names. Template names can change. Some tables store the name from the latest sync, while dc_errors captures a name for each issue. template_id stays the same, so a query that filters on it keeps working after a rename.
  • Use generations when you need individual presentations. It lists each presentation's ID and exact time, which you need for per-generation reporting or for joining to other data. For daily trends, presentation_counts returns far fewer rows.
  • Split group membership before you group by it. user_group_ids and user_groups are comma-joined lists, so grouping on either column groups by the whole combination of a user's groups rather than by one group at a time. Split them into one row per group first, as in the example above. Note that this drops users who belong to no group; switch the CROSS JOIN to a LEFT JOIN ... ON true if you need those rows.
  • Group by user_group_ids, display user_groups. Group names change and are stored as of the last sync. The ids are stable, so group on those and use the names for display.
  • Keep users with no presentation activity. Start from users and LEFT JOIN an activity summary on user_id. Use status = 'done' when you mean successful generations. For standalone, non-email presentations, also exclude attachments and email shells.
  • Use generation_times_daily for trends. It is already rolled up per day and step, so it returns far fewer rows than generation_times and stays well clear of the saved-result cap.
  • Use nested Dynamic Content to query across Analytics Categories. Since a query reads from a single category, combining email and presentation data means referencing one Dynamic Content inside another.

For example, this query keeps users who have no successful standalone generations in the available generations history. Their last_success_at is null:

WITH latest AS (
    SELECT user_id, MAX(created_at) AS last_success_at
    FROM generations
    WHERE status = 'done'
        AND is_attachment = false
        AND presentation_type <> 'email'
    GROUP BY user_id
)
SELECT u.user_id, u.name, u.status, l.last_success_at
FROM users u
LEFT JOIN latest l ON l.user_id = u.user_id
ORDER BY u.user_id
LIMIT 50

Was this article helpful?

Have more questions? Submit a request

Related articles

  • Template Recommendations [Beta]
  • Saving Matik Generated Content
  • [BETA] Version History and Drafts for Matik Mail Templates
  • Using Looker as a Dynamic Content Data Source
  • Adding Loops, Conditionals, and other Logic to Email Templates (Code Editor)

Articles in this section

  • [BETA] Using Matik Analytics as a Data Source
  • [BETA] Draft Versions for Dynamic Content and Inputs
  • Using Asana in Dynamic Content
  • Dynamic Content Overview
  • Version History for Dynamic Content
  • AI Descriptions
  • Using Automated Insights Dynamic Content
  • Working with Images in Dynamic Content
  • Connecting Charts to Dynamic Content in Templates
  • Formulas DC
  • See all articles

Comments

0 comments

Please sign in to leave a comment.

Personalize data-driven content in minutes

Product

  • How it Works
  • Integrations
  • AI Features
  • Security

Solutions

  • Sales
  • Customer Success
  • Ops & Strategy
  • Data

Resources

  • Blog
  • Templates
  • AI + CS Resource Hub
  • Case Studies
  • Help Center

Company

  • About Us
  • Careers
  • Terms of Service
  • Privacy Policy

© 2024 Matik, Inc.