diff --git a/changelog.mdx b/changelog.mdx
index 4ea2799..c8ed466 100644
--- a/changelog.mdx
+++ b/changelog.mdx
@@ -4,6 +4,13 @@ description: "New features, improvements, and fixes across the Sendmux platform.
rss: true
---
+
+ ## Calendar versions in JSON responses
+
+Calendar GET now returns `etag` in the JSON body, matching the `ETag` response header. CLI and MCP callers can use this value in `If-Match` to delete an empty calendar.
+
+
+
## Delete empty mailbox calendars
diff --git a/evidence/sdk-current-contract-897-20261103.md b/evidence/sdk-current-contract-897-20261103.md
new file mode 100644
index 0000000..3a45fbe
--- /dev/null
+++ b/evidence/sdk-current-contract-897-20261103.md
@@ -0,0 +1,19 @@
+# Webhook discriminator snapshot prerequisite
+
+Part of qubrics/qubrics#897. Companion API branch: `agent/sdk-current-contract-897`.
+
+The API producer names its two existing webhook payload variants so the generated snapshot carries explicit `type` discriminator mappings. Previously the inline variants had identical property names and ogen could not infer a discriminator from their enum values. This snapshot is emitted from the producer; no payload fields, event values, prose, hosts or runtime behaviour change.
+
+Validation: API contract regression seen red (exit 1), then green (36/36 including closed-object composition); API `pnpm check:fast` exit 0; `pnpm spec:emit --output /openapi-app.json` exit 0; `npm run postman:emit` exit 0 with no collection diff; `npm run confidentiality:check` exit 0. Normal SDK Go generation against this snapshot exits 0 without generator or current-input binding changes. Remaining generation and CI must pass before delegated merge.
+
+Initial Postman invocation found a missing local dependency (`ERR_MODULE_NOT_FOUND: openapi-to-postmanv2`); `npm ci` installed the committed lockfile, then the same command passed. No dependency changes.
+
+Logs: `/Users/rj/Desktop/GIT-REPOS/sendmux/.claude/artifacts/rebrand-qubrics-897/current-contract-{postman,confidentiality,go}.log`. Source-only merge authorised; no publish or deploy invoked. Placement/polish: generated schema metadata only, no prose or navigation change.
+
+## Approved Calendar GET companion
+
+Ops approved exposing the existing Calendar GET version as JSON `data.etag` and a closed GET-only response component. List/create retain their original schemas. The API producer matches the existing event GET pattern and adds no reads or provider calls. Runtime/schema regressions each failed before the fix, then 80 focused tests, `check:fast` and `spec:check` passed (all exit 0).
+
+Placement: existing generated Mailbox API GET reference and current-day changelog entry. Disposition: keep. Fresh-reader critique corrected the missing empty-calendar qualifier; humanize ran once and protected `etag`, `ETag`, `If-Match`, Calendar GET, CLI and MCP plus body/header equivalence. Final copy passed every applicable rubric line; no navigation changes. Generated snapshot uses the producer output; no hand edits or Postman bypass.
+
+This is a source prerequisite, not publication or deployment proof. Normal SDK generation and exact-head CI remain merge gates.
diff --git a/openapi-app.json b/openapi-app.json
index 3aeac46..60c42b1 100644
--- a/openapi-app.json
+++ b/openapi-app.json
@@ -355,6 +355,43 @@
],
"type": "object"
},
+ "CalendarWebhookEventPayload": {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/CalendarWebhookEventData"
+ },
+ "id": {
+ "description": "Stable per-event identifier. Also sent in the X-Sendmux-Event-Id header.",
+ "example": "evt_clxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "type": "string"
+ },
+ "occurred_at": {
+ "description": "When the event occurred.",
+ "example": "2026-05-21T09:12:00Z",
+ "type": "string"
+ },
+ "team_public_id": {
+ "description": "Team that owns the event.",
+ "example": "team_clxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "type": "string"
+ },
+ "type": {
+ "enum": [
+ "calendar.changed"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "type",
+ "team_public_id",
+ "occurred_at",
+ "data"
+ ],
+ "type": "object"
+ },
"Connection": {
"additionalProperties": false,
"properties": {
@@ -3214,7 +3251,7 @@
{
"properties": {
"data": {
- "$ref": "#/components/schemas/MailboxCalendar"
+ "$ref": "#/components/schemas/MailboxCalendarWithEtag"
},
"meta": {
"$ref": "#/components/schemas/ResponseMeta"
@@ -3228,6 +3265,47 @@
],
"unevaluatedProperties": false
},
+ "MailboxCalendarWithEtag": {
+ "additionalProperties": false,
+ "properties": {
+ "etag": {
+ "description": "Current calendar version, matching the ETag response header. Use this value in If-Match to delete an empty calendar.",
+ "type": "string"
+ },
+ "id": {
+ "description": "Opaque identifier scoped to the selected mailbox.",
+ "maxLength": 4096,
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9_-]+$",
+ "type": "string"
+ },
+ "is_default": {
+ "description": "Whether this is the mailbox's current default calendar.",
+ "type": "boolean"
+ },
+ "mailbox_id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "time_zone": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "mailbox_id",
+ "name",
+ "time_zone",
+ "is_default",
+ "etag"
+ ],
+ "type": "object"
+ },
"MailboxChanges": {
"additionalProperties": false,
"properties": {
@@ -7934,6 +8012,52 @@
],
"unevaluatedProperties": false
},
+ "MailWebhookEventPayload": {
+ "additionalProperties": false,
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/WebhookEventData"
+ },
+ "id": {
+ "description": "Stable per-event identifier. Also sent in the X-Sendmux-Event-Id header.",
+ "example": "evt_clxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "type": "string"
+ },
+ "occurred_at": {
+ "description": "When the event occurred.",
+ "example": "2026-05-21T09:12:00Z",
+ "type": "string"
+ },
+ "team_public_id": {
+ "description": "Team that owns the event.",
+ "example": "team_clxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "type": "string"
+ },
+ "type": {
+ "description": "Dotted event type. Same value as the X-Sendmux-Event-Type header.",
+ "enum": [
+ "message.delivered",
+ "message.bounced",
+ "message.complained",
+ "message.rejected",
+ "message.delivery_delayed",
+ "message.received",
+ "message.received.spam",
+ "sendmux.test"
+ ],
+ "example": "message.received",
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "type",
+ "team_public_id",
+ "occurred_at",
+ "data"
+ ],
+ "type": "object"
+ },
"PatchCalendarEventBody": {
"additionalProperties": false,
"properties": {
@@ -10819,6 +10943,20 @@
},
"WebhookEventPayload": {
"description": "The JSON request body delivered to the webhook endpoint.",
+ "discriminator": {
+ "mapping": {
+ "calendar.changed": "#/components/schemas/CalendarWebhookEventPayload",
+ "message.bounced": "#/components/schemas/MailWebhookEventPayload",
+ "message.complained": "#/components/schemas/MailWebhookEventPayload",
+ "message.delivered": "#/components/schemas/MailWebhookEventPayload",
+ "message.delivery_delayed": "#/components/schemas/MailWebhookEventPayload",
+ "message.received": "#/components/schemas/MailWebhookEventPayload",
+ "message.received.spam": "#/components/schemas/MailWebhookEventPayload",
+ "message.rejected": "#/components/schemas/MailWebhookEventPayload",
+ "sendmux.test": "#/components/schemas/MailWebhookEventPayload"
+ },
+ "propertyName": "type"
+ },
"example": {
"data": {
"bounce_subtype": null,
@@ -10846,87 +10984,10 @@
},
"oneOf": [
{
- "additionalProperties": false,
- "properties": {
- "data": {
- "$ref": "#/components/schemas/WebhookEventData"
- },
- "id": {
- "description": "Stable per-event identifier. Also sent in the X-Sendmux-Event-Id header.",
- "example": "evt_clxxxxxxxxxxxxxxxxxxxxxxxxx",
- "type": "string"
- },
- "occurred_at": {
- "description": "When the event occurred.",
- "example": "2026-05-21T09:12:00Z",
- "type": "string"
- },
- "team_public_id": {
- "description": "Team that owns the event.",
- "example": "team_clxxxxxxxxxxxxxxxxxxxxxxxxx",
- "type": "string"
- },
- "type": {
- "description": "Dotted event type. Same value as the X-Sendmux-Event-Type header.",
- "enum": [
- "message.delivered",
- "message.bounced",
- "message.complained",
- "message.rejected",
- "message.delivery_delayed",
- "message.received",
- "message.received.spam",
- "sendmux.test"
- ],
- "example": "message.received",
- "type": "string"
- }
- },
- "required": [
- "id",
- "type",
- "team_public_id",
- "occurred_at",
- "data"
- ],
- "type": "object"
+ "$ref": "#/components/schemas/MailWebhookEventPayload"
},
{
- "additionalProperties": false,
- "properties": {
- "data": {
- "$ref": "#/components/schemas/CalendarWebhookEventData"
- },
- "id": {
- "description": "Stable per-event identifier. Also sent in the X-Sendmux-Event-Id header.",
- "example": "evt_clxxxxxxxxxxxxxxxxxxxxxxxxx",
- "type": "string"
- },
- "occurred_at": {
- "description": "When the event occurred.",
- "example": "2026-05-21T09:12:00Z",
- "type": "string"
- },
- "team_public_id": {
- "description": "Team that owns the event.",
- "example": "team_clxxxxxxxxxxxxxxxxxxxxxxxxx",
- "type": "string"
- },
- "type": {
- "enum": [
- "calendar.changed"
- ],
- "type": "string"
- }
- },
- "required": [
- "id",
- "type",
- "team_public_id",
- "occurred_at",
- "data"
- ],
- "type": "object"
+ "$ref": "#/components/schemas/CalendarWebhookEventPayload"
}
]
},