with one click
notifly-campaign-conversion-semantics
// Trace how Notifly campaign conversion events are validated, stored, and displayed, especially when multiple conversion events share the same event name but differ by filters.
// Trace how Notifly campaign conversion events are validated, stored, and displayed, especially when multiple conversion events share the same event name but differ by filters.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | notifly-campaign-conversion-semantics |
| description | Trace how Notifly campaign conversion events are validated, stored, and displayed, especially when multiple conversion events share the same event name but differ by filters. |
| version | 1.0.0 |
| author | Hermes Agent |
| license | MIT |
Use this when someone asks questions like:
Answer in three layers:
services/server/web-console/src/schemas/campaign/view/index.tsservices/server/web-console/src/utils/util.tsservices/server/web-console/src/utils/campaign/adapter/index.tsservices/server/web-console/src/pages/api/projects/[projectId]/campaigns/conversion_counts.tsservices/server/web-console/src/repositories/CampaignStatisticRepository.tsservices/server/web-console/src/clients/CampaignClient.tsservices/server/web-console/src/components/campaign/list/CampaignListComponent.tsxservices/server/web-console/src/components/campaign/compose/flow/analytics/FirstConversionEventTip.tsxservices/server/web-console/public/locales/en/products.jsonservices/server/web-console/src/pages/console/products/[productId]/analysis/stats.tsxservices/server/web-console/src/pages/api/lib/message_analytics.tsservices/server/web-console/src/services/CampaignStatisticService.tsservices/server/web-console/src/utils/campaign_stats_utils.tsservices/server/api-service/lib/api/v1/statistics/services/index.jsservices/server/api-service/lib/api/v1/statistics/services/campaignService.jsservices/server/api-service/lib/api/v1/statistics/utils/aggregateMetricByChannel.jsservices/server/web-console/src/repositories/MessageDataRepository.tsservices/server/web-console/src/domains/message-data-viewer/utils/extraction.tsservices/server/web-console/src/domains/message-data-viewer/utils/aggregation.tsservices/server/web-console/src/domains/message-data-viewer/components/MessageDataMetricSelector.tsxeventName + filters, not by event name aloneIn schemas/campaign/view/index.ts, conversion event duplicates are rejected by comparing the canonicalized string form of each conversion event.
That canonical form comes from conversionEventsToString in utils/util.ts:
eventNameeventName?key1=value1&key2=value2Filters are sorted before stringification.
Implication:
This is the key answer when a user asks whether same-name conversions are all "the same".
In utils/campaign/adapter/index.ts, conversion events are stored in conversion_events as objects like:
event_namefiltersparam_key for sales conversionSo internally the identity is not just the bare event name. The filter configuration matters.
The campaign list path is very important:
CampaignListComponent.tsx calls CampaignClient.getCampaignConversionCountscampaigns/conversion_counts.ts explicitly uses campaign.conversion_events?.[0]CampaignStatisticRepository.countConversionByCampaignIdAndEventName(...)So the campaign list conversion metric only uses the first conversion event.
This is also reflected in UI copy:
FirstConversionEventTip.tsxfirst_conversion_tip: "This conversion event will be displayed as a conversion metric in the campaign list."Implication:
In pages/api/lib/message_analytics.ts there is an explicit comment:
// TODO: support multiple conversion eventsconst conversionEvent = metrics[0]?.eventNameSo at least this analytics path only handles the first conversion metric passed into it.
In analysis/stats.tsx, _getConversionEventMetric(campaign) uses campaign?.conversion_event_name, singular, for the displayed conversion metric.
Implication:
For the newer message-data viewer path:
{ name: row.metric_name, type: row.conversion_type, count: ... } via aggregateMetricByChannel.jsconversion_N_name, conversion_N_type, conversion_N_countImportant consequences:
direct, total, sales)metric_name contains the canonical filtered form (e.g. purchase?brand=A), filtered conversions can remain distinguishable herecampaign.conversion_events.map(({ event_name }) => ...), filters are lost in the human-readable summary and same-name conversions can look deceptively identicalTwo high-signal examples:
CampaignStatsDescription.tsx shows campaign.conversion_event_name || campaign.conversion_events?.map(({ event_name }) => event_name).join(', '), so summary text can hide filter differencesmessage-data-viewer/utils/extraction.ts derives available conversion metrics from conv.name, and MessageDataMetricSelector.tsx builds selectable metrics from conversion names × typesOne more caveat: selectionCount.ts currently counts total conversion options as conversionNames.length * 2, while the metric selector actually iterates direct, total, and sales. So the selector/model is event-name-based, but some counting UX still under-assumes conversion variants.
When replying, separate semantic truth from UI truth.
Configuration / semantics
Duplicate rule
Display limitation
같은 이벤트명이어도 필터가 다르면 별도 전환 이벤트로 취급됩니다. 다만 현재 화면에서는 첫 번째 전환 이벤트를 대표 전환 지표로 보여주는 부분이 있어서, 전환 1/전환 4를 각각 완전히 분리해서 확인하는 데는 제한이 있습니다.