# GTM Email Suppression

## Table of Contents

- [GTM Email Suppression](#gtm-email-suppression)
  - [Table of Contents](#table-of-contents)
  - [Introduction](#introduction)
  - [Motivation or Background](#motivation-or-background)
  - [Detailed Design](#detailed-design)
  - [Test Design](#test-design)
    - [Functional Tests](#functional-tests)
    - [Scenario Tests](#scenario-tests)
    - [Compatibility Tests](#compatibility-tests)
  - [Impacts & Risks](#impacts--risks)
  - [Unresolved Questions](#unresolved-questions)

## Introduction

This document describes the DevOps-side GTM email suppression API used during the NA GTM migration. The API lets an external sender claim the right to send a tenant and campaign email, cancels any currently queued DevOps email for that tenant, and prevents DevOps from queueing the same campaign later.

## Motivation or Background

DevOps currently sends GTM emails from an event-driven pipeline. The NA GTM system sends overlapping emails from BigQuery polling. During migration, both systems can qualify the same tenant, which creates duplicate-send risk.

There are three timing cases to handle:

- DevOps already has a queued email. DevOps can cancel it before the queue processor sends it.
- DevOps already moved the email to sending or sent. DevOps cannot safely cancel it, and NA should skip sending.
- DevOps has not processed the event yet. There is no queued row to cancel, but DevOps may queue later unless a durable suppression record exists.

Because the third case is not representable in `gtm_sent_emails`, the implementation adds a separate suppression table. `gtm_sent_emails` remains the delivery history and queue table; `gtm_email_suppressions` records external claims for future queue prevention.

## Detailed Design

The public API route is:

```http
POST /api/v1/devops/userbehavior/gtm/emails/suppress
```

Request body:

```json
{
  "tenant_id": 123,
  "campaign_name": "qualified-signup-followup",
  "source": "na-gtm-engineering",
  "reason": "external_sender_claimed"
}
```

Response body:

```json
{
  "errcode": "OK",
  "errmsg": "",
  "trace_id": "...",
  "status": "cancelled",
  "cancelled_count": 1
}
```

`status` can be:

- `cancelled`: at least one DevOps queued email was cancelled, and NA may send.
- `not_found_but_suppressed`: no queued/sending/sent DevOps email exists, but the suppression claim was persisted, and NA may send.
- `already_sent_or_sending`: DevOps has already started or completed sending, and NA should skip.

The new table is `gtm_email_suppressions`:

```text
id
created_at
updated_at
tenant_id
campaign_name
source
reason
```

`tenant_id + campaign_name` is unique. Repeated calls from NA are idempotent: the existing suppression row is updated with the latest `source`, `reason`, and `updated_at`.

The suppress flow is:

1. Validate `tenant_id`, `campaign_name`, `source`, and `reason`.
2. Upsert `gtm_email_suppressions(tenant_id, campaign_name, source, reason)`.
3. Update existing queued DevOps emails for the tenant:

```sql
UPDATE gtm_sent_emails
SET status = 'cancelled'
WHERE tenant_id = ? AND status = 'queued';
```

4. Check whether any DevOps email for the tenant is already `sending` or `sent`.
5. Return one of the status values above.

`gtm_sent_emails` does not add `campaign_name`. Current DevOps GTM delivery only has the qualified signup followup path, so cancelling by `tenant_id + status=queued` is sufficient for the migration use case. Future unrelated email flows should not call this suppression check unless they intentionally share the same campaign ownership semantics.

The queue flow is updated in `queueGTMEmail`:

1. Before inserting a queued email, check `gtm_email_suppressions` for:

```text
tenant_id = lead.TenantID
campaign_name = qualified-signup-followup
```

2. If a suppression row exists, skip inserting into `gtm_sent_emails`.
3. If no suppression row exists, create the queued email as before.

## Test Design

### Functional Tests

Repository tests cover:

- cancelling queued emails by tenant;
- preserving sending emails when cancelling queued rows;
- detecting existing sending or sent rows;
- not cancelling emails for other tenants.

Service tests cover:

- suppressing a tenant prevents future `queueGTMEmail` inserts;
- `cancelled` is returned when an existing queued row is cancelled;
- `already_sent_or_sending` is returned when sending work already started;
- `not_found_but_suppressed` is returned when no current email row exists.

### Scenario Tests

The expected NA flow is:

1. NA qualifies a signup.
2. NA calls the DevOps suppress API.
3. If status is `cancelled` or `not_found_but_suppressed`, NA sends the email.
4. If status is `already_sent_or_sending`, NA skips sending.
5. If the API call fails, NA should retry or skip sending rather than default-send.

The DevOps event flow is:

1. DevOps receives and enriches the signup event.
2. DevOps qualifies and matches the GTM lead.
3. DevOps checks `gtm_email_suppressions` before queueing.
4. If suppressed, DevOps does not insert a queued sent-email row.

### Compatibility Tests

The migration only creates `gtm_email_suppressions`; it does not alter `gtm_sent_emails`. Existing queued email processing remains compatible with current rows and statuses.

Swagger docs are generated for the new API route and request/response structs. Existing subscription APIs keep the same routes and payloads.

## Impacts & Risks

Impacts:

- Prevents duplicate GTM emails during NA migration, including the race where NA claims before DevOps has queued anything.
- Keeps delivery history and suppression claims separated, so `gtm_sent_emails` remains a delivery queue/history table.
- Adds a small read before each qualified signup followup queue insert.

Risks:

- Since existing queued cancellation is tenant-level, future reuse of `gtm_sent_emails` for multiple simultaneous GTM campaigns could cause the suppress API to cancel another queued GTM email for the same tenant. If that reuse happens, add a campaign discriminator then.
- NA must treat API failures conservatively. Default-send on suppress API failure can reintroduce duplicate sends.
- The API is under the authenticated `/api/v1/devops` route, so NA callers need a valid DevOps auth path.

## Unresolved Questions

- Whether NA should call with an end-user token, service account, or another DevOps-authenticated credential.
- Whether suppression claims should expire after the migration window or remain permanently as an audit trail.
- Whether future GTM campaigns should reuse `gtm_sent_emails` or introduce campaign-specific queue/history tables.
