Skip to main content

Report on a campaign with App Events

In this tutorial, you'll report a campaign's email, SMS, and push funnel with App Events. A funnel counts the messages at each stage, such as sent, delivered, opened, and clicked. A send is one message, such as an announcement email, that your app sends to the campaign's audience on one channel. Your app reads every funnel stage with one query, and the dashboard shows each day's sends beside that day's sales.

You'll learn how to:

  • Declare the events that your app sends in an Analytics App Events extension.
  • Report the messages that your app sends, with the attributes that the standard event registry defines.
  • Query the app_events schema for one campaign's funnel.
  • Flag a delivery problem in the events that your app reports.
  • Summarize all the store's campaigns on one page.

Requirements

Complete part two

You've built the campaign dashboard from part one and added the targets and annotations from part two. Your app has the part two access scopes and Level 2 access to protected customer data.

Update Shopify CLI

You're using the latest version of Shopify CLI.

Project

Anchor to Report the campaign funnel to App EventsReport the campaign funnel to App Events

Declare the marketing events that your app reports from the standard event registry, and then post one event for each recipient at each stage.

Anchor to Add the App Events filesAdd the App Events files

Copy these four files from the sample repository's main branch into your app directory:

  • extensions/app-events/shopify.extension.toml declares the standard events that your app sends.
  • app/app-events.server.js defines the campaign's sends and posts them.
  • app/panels/app-events.jsx renders the funnel panels.
  • app/panels/incident.jsx renders the bounce banner and the Flag incident button.

Then replace app/campaign-report.server.js and app/campaign-mutations.server.js with the versions from the sample repository's main branch.

Anchor to Mount the App Events panelsMount the App Events panels

Replace app/routes/app.campaigns.$campaignId.jsx with the version from the sample repository's main branch. The route adds the send controls, the incident banner, and the Channel metrics, Send performance, and Deliverability panels.

A planned campaign's page also has a QR codes section. The section shows each assigned code's image and links to the new QR code form, which preselects the campaign. A canceled campaign's page has Delete campaign. The button deletes the campaign entry and the campaign's CampaignState row.

Run shopify app dev from your app's directory. If the command is already running, then stop it and run it again. Shopify CLI loads the App Events extension when you start the app:

Terminal

shopify app dev

Anchor to Declare the events and deploy themDeclare the events and deploy them

extensions/app-events/shopify.extension.toml lists each event by the event's registry handle. With shopify app dev running, open a second terminal and deploy the extension. Shopify validates the declaration:

Terminal

shopify app deploy

  • Shopify rejects a deploy that renames or drops an event that you've released.
  • Shopify Analytics makes a standard event available only after your app declares and releases it.

Anchor to Check the App Events schemaCheck the App Events schema

Press g in the shopify app dev terminal to open GraphiQL, and then query a metric that your extension declares:

GraphQL

query {
shopifyqlQuery(
query: "FROM app_events SHOW emails_sent SINCE -29d UNTIL today"
) {
tableData {
columns {
name
dataType
displayName
}
rows
}
parseErrors
}
}

A Column Not Found parse error means that the store doesn't have your event declaration yet. After your app sends events, confirm the requests in the Dev Dashboard.


Anchor to Get an App Events tokenGet an App Events token

App Events uses an app-level token, separate from the admin session. app/app-events.server.js requests the token with your app's client ID and client secret in a client_credentials grant.


Anchor to Describe the campaign as sends and stagesDescribe the campaign as sends and stages

Each attribute on an event becomes a column that you can group by. Your app puts six shared attributes on every event: the channel, message type, flow ID, template ID, consent state, and the campaign ID from the campaign entry.


  • Keep names, email addresses, phone numbers, and other personal data out of the attribute values. Use anonymized identifiers instead.

Anchor to Never reuse a campaign IDNever reuse a campaign ID

If your app reuses an existing campaign's ID for a new campaign, then the new campaign's queries return the old campaign's events. The App Events API can't retract an event.

allocateCampaignId adds one to the app's stored ID or to a higher ID on the store's campaign entries. The increment is atomic. Two requests never get the same ID.

If another entry already uses the new entry's handle, then Shopify adds a suffix to the handle. createCampaign deletes the suffixed entry and retries with the next ID.

Anchor to Post one event for each recipientPost one event for each recipient

Post one event in each request to the events endpoint of the App Events API, and send the requests in sequence. A 202 response means that Shopify received the event.

Shopify counts requests with the same idempotency_key within 24 hours as one event. Compose one key of at most 64 characters for each recipient at each stage.

When a request gets a 409, 429, or 5xx response, or the connection drops, sendWithRetry retries the request twice with the same idempotency key. The send stops at the first event that still fails.


Anchor to Replay the sends from the appReplay the sends from the app

The sample app replays a fixed schedule of sends from the campaign page. A production app posts each event as the send happens. Click Send events, confirm, and reload the page after a few minutes to see Channel metrics.

The replay spreads its sends over the 14 days after the campaign's start date. Allow a replay only for an active campaign that runs at least 14 days and started at least 14 days ago. A shorter or newer campaign gets sends dated after its end date or after today.

If the app user replays the sends after the 24-hour idempotency window, then Shopify counts every event twice. With claimSend, only one request can run the campaign's send. If the send fails to start or Shopify receives no events, then your app releases the claim and the app user can send again.

Anchor to Query the funnel with ShopifyQLQuery the funnel with ShopifyQL

Query app_events the same way that you query sales, and filter on the campaign ID that your app attached to each event.

Request the campaign's funnel metrics in one statement, and use TIMESERIES day to return a row for each day. The query covers the campaign's dates within the last 30 days.


  • Shopify builds the app_events columns for each store from the events that the store's apps declare.
  • Limit a query that filters or groups by your attributes to the last 30 days. Shopify keeps the attribute values for only 30 days.

Anchor to Total the funnel across the campaignTotal the funnel across the campaign

Sum each metric across the daily rows for the campaign's totals and rates. Then add up each day's sends across channels for the Messages sent column in the sales table.

Anchor to Break the funnel down by channel and by sendBreak the funnel down by channel and by send

Each event includes the channel and the send as attributes. Group by the attributes to compare channels and sends.

Anchor to Report bounces and unsubscribes by channelReport bounces and unsubscribes by channel

Deliverability shows the bounces and unsubscribes for each channel. The sample app doesn't report SMS bounces, and the standard event registry doesn't define push bounce or unsubscribe events. The panel shows Not reported for a missing count.

Anchor to Group the funnel by the attributes that you sentGroup the funnel by the attributes that you sent

A GROUP BY template_id clause returns one row for each send. Filter on app_name too, because two apps can use the same campaign ID. Your app reads its name from currentAppInstallation.app.title:

ShopifyQL

FROM app_events
SHOW emails_sent, emails_delivered, emails_opened, email_unsubscribes
WHERE campaign_id = 4210
AND app_name = 'Campaign dashboard'
GROUP BY template_id
SINCE -29d UNTIL today
ORDER BY template_id ASC

The sample app's query requests every sequence metric from app/channels.js, including the SMS and push metrics.

To label and order the rows, convert each template ID to a number. Look up the number in the send sequence in app/app-events.server.js. For example, the send sequence labels template 4101 as Announcement.

Run a separate bounce query that groups by recipient_status to separate permanent failures from temporary failures.

Anchor to List reach and engagement by sendList reach and engagement by send

Render one row for each send with the reached count and an engagement rate. Leave bounces to the Deliverability panel.

Open the campaign. If the funnel shows all zeros after a few minutes, then check your app's event requests in the Dev Dashboard against the App Events requirements.

Anchor to Flag a problem in the events that your app reportedFlag a problem in the events that your app reported

When the funnel shows bounced emails, show a banner with the bounce rate. The banner shows the top bounce reason from the recipient_status rows of the bounce query. The banner shows a Flag incident button while the campaign is active and today falls inside the campaign's dates. The button adds an other annotation to the store's charts. Click Flag incident, and the banner shows Incident flagged.


  • On the server, rerun the funnel and bounce queries, and build the annotation's description from the bounce rate and each recipient_status.
  • Record the flag's annotation ID under incident:<day> in the campaign's markers in your app's database. Your app rejects a second flag for the same day.

Anchor to Report the campaigns on one pageReport the campaigns on one page

The campaign list summarizes every campaign with one App Events query that groups by campaign_id and app_name. Replace app/routes/app.campaigns._index.jsx with the version from the sample repository's main branch. The list shows a new Messages sent column, and Messages sent by campaign appears after the first replay.

Anchor to Read the targets and sends for every campaignRead the targets and sends for every campaign

readAllTargets collects the target IDs that your app's database stores for each campaign and removes the duplicates. It passes the IDs to readTargets, which sends the IDs to the nodes query in batches of 250.

Group one App Events query by campaign_id and app_name. A campaign ID is unique only within one app.


Anchor to Summarize the campaigns in one tableSummarize the campaigns in one table

Give each campaign a row with the dates, segment, sends, target progress, and annotation and target counts. Report progress against only the store sales target, which already includes product and QR code sales.

Convert the event row's campaign_id and the campaign entry's campaign_id to strings before you compare them.

Anchor to Report the sends without another requestReport the sends without another request

Pivot the sends from the campaign list's App Events query into one Messages sent by campaign table, with a row for each campaign and a column for each channel.


  • If no campaign has any sends, then the pivot produces a table of zeros. Render the table only after at least one row contains a send.
  • Mount each metric card after window.shopify.shopifyQL resolves, or the card stays in an error state.

Confirm that your app's events and annotations reach Shopify Analytics.

Anchor to Run the app and check the panelsRun the app and check the panels

Press p in the shopify app dev terminal to open the preview URL.

Open an active campaign that started 14 to 28 days ago and ends after today. To create one, give the campaign a start date 14 to 28 days ago and an end date after today. Then click Activate campaign.

If you haven't sent the campaign's events yet, then click Send events and confirm. Reload the page after a few minutes.

The sales table shows a new Messages sent column. Channel metrics breaks the stages out by channel. Send performance lists each send's reach and engagement. Deliverability totals the bounces and unsubscribes.


  • The sales panels show data only when the store has orders inside the campaign's dates. Bogus Gateway test orders don't reach sales. On a development store, create orders with the orderCreate mutation and a processedAt date inside the campaign's dates.
  • The example app's README describes a campaign with sample data, from start to close.

Anchor to Part three complete!Part three complete!

You built a campaign dashboard that reports a campaign end to end. The app measures the campaign's sales against the previous period, the previous year, and a target. The app marks key dates on the store's analytics timeline. The app reports email, SMS, and push sends as App Events.

The dashboard shows the sends beside the store's sales. App users can flag delivery incidents from the dashboard. The dashboard also summarizes every campaign on one page.

Was this page helpful?