@primitivedotdev/sdk 0.31.6 → 0.31.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,6 +57,10 @@ const openapiDocument = {
57
57
  "name": "Sending",
58
58
  "description": "Send outbound emails through the Primitive API"
59
59
  },
60
+ {
61
+ "name": "Threads",
62
+ "description": "Conversation threads spanning received and sent emails"
63
+ },
60
64
  {
61
65
  "name": "Endpoints",
62
66
  "description": "Manage webhook endpoints that receive email events"
@@ -1552,6 +1556,27 @@ const openapiDocument = {
1552
1556
  "404": { "$ref": "#/components/responses/NotFound" }
1553
1557
  }
1554
1558
  } },
1559
+ "/threads/{id}": {
1560
+ "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
1561
+ "get": {
1562
+ "operationId": "getThread",
1563
+ "summary": "Get a conversation thread by id",
1564
+ "description": "Returns a conversation thread: its metadata plus the inbound\nand outbound messages that belong to it, interleaved in time\norder (oldest first). A thread spans both received emails and\nyour sends, so an agent can reconstruct an entire back-and-forth\nfrom one call instead of walking reply headers.\n\nEach message carries a `direction` (`inbound` | `outbound`) and\nan `id`; fetch the full message via `/emails/{id}` or\n`/sent-emails/{id}` accordingly. Bodies are omitted here to keep\nthe thread view lightweight.\n\nDiscover a thread id from the `thread_id` field on any email or\nsent-email (list or detail). The message list is capped; compare\n`message_count` against `messages.length` to detect truncation.\n",
1565
+ "tags": ["Threads"],
1566
+ "responses": {
1567
+ "200": {
1568
+ "description": "Thread detail",
1569
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
1570
+ "type": "object",
1571
+ "properties": { "data": { "$ref": "#/components/schemas/Thread" } }
1572
+ }] } } }
1573
+ },
1574
+ "400": { "$ref": "#/components/responses/ValidationError" },
1575
+ "401": { "$ref": "#/components/responses/Unauthorized" },
1576
+ "404": { "$ref": "#/components/responses/NotFound" }
1577
+ }
1578
+ }
1579
+ },
1555
1580
  "/functions": {
1556
1581
  "get": {
1557
1582
  "operationId": "listFunctions",
@@ -3419,7 +3444,12 @@ const openapiDocument = {
3419
3444
  },
3420
3445
  "raw_size_bytes": { "type": ["integer", "null"] },
3421
3446
  "webhook_status": { "$ref": "#/components/schemas/EmailWebhookStatus" },
3422
- "webhook_attempt_count": { "type": "integer" }
3447
+ "webhook_attempt_count": { "type": "integer" },
3448
+ "thread_id": {
3449
+ "type": ["string", "null"],
3450
+ "format": "uuid",
3451
+ "description": "Conversation thread this message belongs to. Fetch\n`/threads/{thread_id}` for the full ordered thread. NULL on\nmessages received before threading was enabled.\n"
3452
+ }
3423
3453
  },
3424
3454
  "required": [
3425
3455
  "id",
@@ -3638,6 +3668,19 @@ const openapiDocument = {
3638
3668
  "type": ["string", "null"],
3639
3669
  "format": "uuid",
3640
3670
  "description": "The `sent_emails.id` of the outbound this inbound was a\nreply to, when resolvable. Set at inbound ingest by\nmatching the parsed In-Reply-To (or References, as a\nfallback) against `sent_emails.message_id` in the same\norg. The mirror of `sent_emails.in_reply_to_email_id` for\nthe inbound side of a thread. NULL when the inbound is\nnot a threaded reply to one of your sends, when neither\nheader survived the path through intermediate MTAs, or on\ninbound received before this auto-link landed.\n"
3671
+ },
3672
+ "thread_id": {
3673
+ "type": ["string", "null"],
3674
+ "format": "uuid",
3675
+ "description": "Conversation thread this message belongs to. Inbound and\noutbound messages in the same conversation share a\n`thread_id`; fetch `/threads/{thread_id}` for the full\nordered thread. Assigned at ingest. NULL on messages\nreceived before threading was enabled (until backfilled).\n"
3676
+ },
3677
+ "parsed": {
3678
+ "allOf": [{ "$ref": "#/components/schemas/ParsedEmailData" }],
3679
+ "description": "Parsed MIME content (addresses, threading headers,\nattachment metadata), matching the `email.parsed` object\non the webhook payload so one parser handles both the\nwebhook and this endpoint. The top-level `body_text` /\n`body_html` fields above are the same values as\n`parsed.body_text` / `parsed.body_html`, retained for\nbackward compatibility.\n"
3680
+ },
3681
+ "auth": {
3682
+ "allOf": [{ "$ref": "#/components/schemas/EmailAuth" }],
3683
+ "description": "SPF / DKIM / DMARC verdicts computed at ingest, matching\nthe `email.auth` object on the webhook payload. Use these\nto decide how much to trust a message before acting on\ninstructions it contains.\n"
3641
3684
  }
3642
3685
  },
3643
3686
  "required": [
@@ -3651,7 +3694,9 @@ const openapiDocument = {
3651
3694
  "webhook_attempt_count",
3652
3695
  "from_email",
3653
3696
  "to_email",
3654
- "replies"
3697
+ "replies",
3698
+ "parsed",
3699
+ "auth"
3655
3700
  ]
3656
3701
  },
3657
3702
  "EmailDetailReply": {
@@ -3684,6 +3729,241 @@ const openapiDocument = {
3684
3729
  "created_at"
3685
3730
  ]
3686
3731
  },
3732
+ "EmailAddress": {
3733
+ "type": "object",
3734
+ "description": "A parsed RFC 5322 address with optional display name.",
3735
+ "properties": {
3736
+ "name": {
3737
+ "type": ["string", "null"],
3738
+ "description": "Display name, when present (e.g. `Alice Example`)."
3739
+ },
3740
+ "address": {
3741
+ "type": "string",
3742
+ "description": "Bare email address (e.g. `alice@example.com`)."
3743
+ }
3744
+ },
3745
+ "required": ["address"]
3746
+ },
3747
+ "EmailAttachment": {
3748
+ "type": "object",
3749
+ "description": "Metadata for one attachment. The bytes are not inline; download\nall attachments for a message as a gzipped tarball via\n`/emails/{id}/attachments.tar.gz`. `sha256` lets you verify a\nspecific part after extraction.\n",
3750
+ "properties": {
3751
+ "filename": { "type": ["string", "null"] },
3752
+ "content_type": { "type": ["string", "null"] },
3753
+ "size_bytes": { "type": "integer" },
3754
+ "sha256": { "type": ["string", "null"] },
3755
+ "part_index": {
3756
+ "type": "integer",
3757
+ "description": "Zero-based index of this part within the message."
3758
+ }
3759
+ },
3760
+ "required": ["size_bytes"]
3761
+ },
3762
+ "ParsedEmailData": {
3763
+ "type": "object",
3764
+ "description": "Parsed MIME content for an inbound email. Mirrors the\n`email.parsed` object on the webhook payload so a single parser\nhandles both surfaces. `status` is `complete` when parsing\nsucceeded; on `failed` the body/address/attachment fields are\nabsent and `error` describes why.\n",
3765
+ "properties": {
3766
+ "status": {
3767
+ "type": "string",
3768
+ "enum": ["complete", "failed"]
3769
+ },
3770
+ "body_text": {
3771
+ "type": ["string", "null"],
3772
+ "description": "Plain-text body. Present when `status` is `complete`."
3773
+ },
3774
+ "body_html": {
3775
+ "type": ["string", "null"],
3776
+ "description": "HTML body. Present when `status` is `complete`."
3777
+ },
3778
+ "reply_to": {
3779
+ "type": ["array", "null"],
3780
+ "items": { "$ref": "#/components/schemas/EmailAddress" },
3781
+ "description": "Parsed `Reply-To` header addresses."
3782
+ },
3783
+ "cc": {
3784
+ "type": ["array", "null"],
3785
+ "items": { "$ref": "#/components/schemas/EmailAddress" },
3786
+ "description": "Parsed `Cc` header addresses."
3787
+ },
3788
+ "bcc": {
3789
+ "type": ["array", "null"],
3790
+ "items": { "$ref": "#/components/schemas/EmailAddress" },
3791
+ "description": "Parsed `Bcc` header addresses (rarely present on inbound)."
3792
+ },
3793
+ "to_addresses": {
3794
+ "type": ["array", "null"],
3795
+ "items": { "$ref": "#/components/schemas/EmailAddress" },
3796
+ "description": "Parsed `To` header addresses."
3797
+ },
3798
+ "in_reply_to": {
3799
+ "type": ["array", "null"],
3800
+ "items": { "type": "string" },
3801
+ "description": "Message-IDs from the `In-Reply-To` header."
3802
+ },
3803
+ "references": {
3804
+ "type": ["array", "null"],
3805
+ "items": { "type": "string" },
3806
+ "description": "Message-IDs from the `References` header."
3807
+ },
3808
+ "attachments": {
3809
+ "type": "array",
3810
+ "items": { "$ref": "#/components/schemas/EmailAttachment" },
3811
+ "description": "Attachment metadata. Empty array when none."
3812
+ },
3813
+ "error": {
3814
+ "type": ["object", "null"],
3815
+ "description": "Present (non-null) only when `status` is `failed`. When\npresent, all three fields are populated, so a consumer can\nbranch on `code` without defensive null checks.\n",
3816
+ "properties": {
3817
+ "code": {
3818
+ "type": "string",
3819
+ "description": "Stable failure code (e.g. `PARSE_FAILED`)."
3820
+ },
3821
+ "message": { "type": "string" },
3822
+ "retryable": { "type": "boolean" }
3823
+ },
3824
+ "required": [
3825
+ "code",
3826
+ "message",
3827
+ "retryable"
3828
+ ]
3829
+ }
3830
+ },
3831
+ "required": ["status"]
3832
+ },
3833
+ "DkimSignature": {
3834
+ "type": "object",
3835
+ "description": "One DKIM signature found on the message, with its verdict.",
3836
+ "properties": {
3837
+ "domain": { "type": "string" },
3838
+ "selector": { "type": "string" },
3839
+ "result": {
3840
+ "type": "string",
3841
+ "description": "Verification result (e.g. `pass`, `fail`, `none`)."
3842
+ },
3843
+ "aligned": {
3844
+ "type": "boolean",
3845
+ "description": "Whether the signing domain aligns with the From domain (for DMARC)."
3846
+ },
3847
+ "keyBits": { "type": ["integer", "null"] },
3848
+ "algo": { "type": ["string", "null"] }
3849
+ },
3850
+ "required": [
3851
+ "domain",
3852
+ "selector",
3853
+ "result",
3854
+ "aligned"
3855
+ ]
3856
+ },
3857
+ "EmailAuth": {
3858
+ "type": "object",
3859
+ "description": "SPF / DKIM / DMARC verdicts computed at ingest. Mirrors the\n`email.auth` object on the webhook payload. Field names are\ncamelCase to match that payload exactly. For messages received\nbefore auth was recorded, the verdicts default to `none`.\n",
3860
+ "properties": {
3861
+ "spf": {
3862
+ "type": "string",
3863
+ "description": "SPF result (e.g. `pass`, `fail`, `softfail`, `none`)."
3864
+ },
3865
+ "dmarc": {
3866
+ "type": "string",
3867
+ "description": "DMARC result (e.g. `pass`, `fail`, `none`)."
3868
+ },
3869
+ "dmarcPolicy": {
3870
+ "type": ["string", "null"],
3871
+ "description": "Published DMARC policy (`none`, `quarantine`, `reject`)."
3872
+ },
3873
+ "dmarcFromDomain": {
3874
+ "type": ["string", "null"],
3875
+ "description": "The From-header domain DMARC was evaluated against."
3876
+ },
3877
+ "dmarcSpfAligned": { "type": "boolean" },
3878
+ "dmarcDkimAligned": { "type": "boolean" },
3879
+ "dmarcSpfStrict": { "type": ["boolean", "null"] },
3880
+ "dmarcDkimStrict": { "type": ["boolean", "null"] },
3881
+ "dkimSignatures": {
3882
+ "type": "array",
3883
+ "items": { "$ref": "#/components/schemas/DkimSignature" }
3884
+ }
3885
+ },
3886
+ "required": [
3887
+ "spf",
3888
+ "dmarc",
3889
+ "dmarcSpfAligned",
3890
+ "dmarcDkimAligned",
3891
+ "dkimSignatures"
3892
+ ]
3893
+ },
3894
+ "Thread": {
3895
+ "type": "object",
3896
+ "description": "A conversation thread: its metadata plus the inbound and\noutbound messages that belong to it, interleaved oldest-first.\nMembership is the stored `thread_id` on each message. Bodies are\nomitted here to keep the thread view lightweight; fetch\n`/emails/{id}` or `/sent-emails/{id}` for a single message's\nfull content.\n",
3897
+ "properties": {
3898
+ "id": {
3899
+ "type": "string",
3900
+ "format": "uuid"
3901
+ },
3902
+ "subject": {
3903
+ "type": ["string", "null"],
3904
+ "description": "Normalized subject of the thread (Re/Fwd prefixes stripped)."
3905
+ },
3906
+ "root_message_id": {
3907
+ "type": ["string", "null"],
3908
+ "description": "Message-ID of the conversation root, when known."
3909
+ },
3910
+ "message_count": {
3911
+ "type": "integer",
3912
+ "description": "Total messages in the thread. `messages` is capped (most\nrecent first, then re-sorted oldest-first), so\n`message_count > messages.length` signals truncation.\n"
3913
+ },
3914
+ "first_message_at": {
3915
+ "type": ["string", "null"],
3916
+ "format": "date-time"
3917
+ },
3918
+ "last_message_at": {
3919
+ "type": ["string", "null"],
3920
+ "format": "date-time"
3921
+ },
3922
+ "created_at": {
3923
+ "type": "string",
3924
+ "format": "date-time"
3925
+ },
3926
+ "messages": {
3927
+ "type": "array",
3928
+ "items": { "$ref": "#/components/schemas/ThreadMessage" }
3929
+ }
3930
+ },
3931
+ "required": [
3932
+ "id",
3933
+ "message_count",
3934
+ "created_at",
3935
+ "messages"
3936
+ ]
3937
+ },
3938
+ "ThreadMessage": {
3939
+ "type": "object",
3940
+ "description": "One message in a thread (inbound or outbound).",
3941
+ "properties": {
3942
+ "direction": {
3943
+ "type": "string",
3944
+ "enum": ["inbound", "outbound"],
3945
+ "description": "`inbound` for a received email (`/emails/{id}`), `outbound`\nfor a send (`/sent-emails/{id}`). Use it with `id` to fetch\nfull content from the right endpoint.\n"
3946
+ },
3947
+ "id": {
3948
+ "type": "string",
3949
+ "format": "uuid"
3950
+ },
3951
+ "message_id": { "type": ["string", "null"] },
3952
+ "from": { "type": ["string", "null"] },
3953
+ "to": { "type": ["string", "null"] },
3954
+ "subject": { "type": ["string", "null"] },
3955
+ "status": {
3956
+ "type": ["string", "null"],
3957
+ "description": "Lifecycle status (an EmailStatus or SentEmailStatus value, per `direction`)."
3958
+ },
3959
+ "timestamp": {
3960
+ "type": ["string", "null"],
3961
+ "format": "date-time",
3962
+ "description": "received_at for inbound, created_at for outbound."
3963
+ }
3964
+ },
3965
+ "required": ["direction", "id"]
3966
+ },
3687
3967
  "SendMailAttachment": {
3688
3968
  "type": "object",
3689
3969
  "additionalProperties": false,
@@ -3900,6 +4180,11 @@ const openapiDocument = {
3900
4180
  "format": "uuid",
3901
4181
  "description": "Reference to the inbound `emails.id` that this send\nreplied to, when known. Populated when the caller used\n/emails/{id}/reply or when /send-mail's `in_reply_to`\nmatched a stored inbound message_id in the same org.\n"
3902
4182
  },
4183
+ "thread_id": {
4184
+ "type": ["string", "null"],
4185
+ "format": "uuid",
4186
+ "description": "Conversation thread this send belongs to. A reply inherits\nthe thread of the inbound it answers; a fresh send starts a\nnew thread. Fetch `/threads/{thread_id}` for the full\nordered thread (inbound + outbound interleaved). NULL on\ngate-denied sends and on sends created before threading was\nenabled.\n"
4187
+ },
3903
4188
  "queue_id": {
3904
4189
  "type": ["string", "null"],
3905
4190
  "description": "Message identifier assigned by Primitive's outbound\nrelay once the agent accepts the message. Null on\nqueued, gate_denied, and agent_failed rows.\n"
@@ -7001,6 +7286,218 @@ const operationManifest = [
7001
7286
  "type": ["string", "null"],
7002
7287
  "format": "uuid",
7003
7288
  "description": "The `sent_emails.id` of the outbound this inbound was a\nreply to, when resolvable. Set at inbound ingest by\nmatching the parsed In-Reply-To (or References, as a\nfallback) against `sent_emails.message_id` in the same\norg. The mirror of `sent_emails.in_reply_to_email_id` for\nthe inbound side of a thread. NULL when the inbound is\nnot a threaded reply to one of your sends, when neither\nheader survived the path through intermediate MTAs, or on\ninbound received before this auto-link landed.\n"
7289
+ },
7290
+ "thread_id": {
7291
+ "type": ["string", "null"],
7292
+ "format": "uuid",
7293
+ "description": "Conversation thread this message belongs to. Inbound and\noutbound messages in the same conversation share a\n`thread_id`; fetch `/threads/{thread_id}` for the full\nordered thread. Assigned at ingest. NULL on messages\nreceived before threading was enabled (until backfilled).\n"
7294
+ },
7295
+ "parsed": {
7296
+ "allOf": [{
7297
+ "type": "object",
7298
+ "description": "Parsed MIME content for an inbound email. Mirrors the\n`email.parsed` object on the webhook payload so a single parser\nhandles both surfaces. `status` is `complete` when parsing\nsucceeded; on `failed` the body/address/attachment fields are\nabsent and `error` describes why.\n",
7299
+ "properties": {
7300
+ "status": {
7301
+ "type": "string",
7302
+ "enum": ["complete", "failed"]
7303
+ },
7304
+ "body_text": {
7305
+ "type": ["string", "null"],
7306
+ "description": "Plain-text body. Present when `status` is `complete`."
7307
+ },
7308
+ "body_html": {
7309
+ "type": ["string", "null"],
7310
+ "description": "HTML body. Present when `status` is `complete`."
7311
+ },
7312
+ "reply_to": {
7313
+ "type": ["array", "null"],
7314
+ "items": {
7315
+ "type": "object",
7316
+ "description": "A parsed RFC 5322 address with optional display name.",
7317
+ "properties": {
7318
+ "name": {
7319
+ "type": ["string", "null"],
7320
+ "description": "Display name, when present (e.g. `Alice Example`)."
7321
+ },
7322
+ "address": {
7323
+ "type": "string",
7324
+ "description": "Bare email address (e.g. `alice@example.com`)."
7325
+ }
7326
+ },
7327
+ "required": ["address"]
7328
+ },
7329
+ "description": "Parsed `Reply-To` header addresses."
7330
+ },
7331
+ "cc": {
7332
+ "type": ["array", "null"],
7333
+ "items": {
7334
+ "type": "object",
7335
+ "description": "A parsed RFC 5322 address with optional display name.",
7336
+ "properties": {
7337
+ "name": {
7338
+ "type": ["string", "null"],
7339
+ "description": "Display name, when present (e.g. `Alice Example`)."
7340
+ },
7341
+ "address": {
7342
+ "type": "string",
7343
+ "description": "Bare email address (e.g. `alice@example.com`)."
7344
+ }
7345
+ },
7346
+ "required": ["address"]
7347
+ },
7348
+ "description": "Parsed `Cc` header addresses."
7349
+ },
7350
+ "bcc": {
7351
+ "type": ["array", "null"],
7352
+ "items": {
7353
+ "type": "object",
7354
+ "description": "A parsed RFC 5322 address with optional display name.",
7355
+ "properties": {
7356
+ "name": {
7357
+ "type": ["string", "null"],
7358
+ "description": "Display name, when present (e.g. `Alice Example`)."
7359
+ },
7360
+ "address": {
7361
+ "type": "string",
7362
+ "description": "Bare email address (e.g. `alice@example.com`)."
7363
+ }
7364
+ },
7365
+ "required": ["address"]
7366
+ },
7367
+ "description": "Parsed `Bcc` header addresses (rarely present on inbound)."
7368
+ },
7369
+ "to_addresses": {
7370
+ "type": ["array", "null"],
7371
+ "items": {
7372
+ "type": "object",
7373
+ "description": "A parsed RFC 5322 address with optional display name.",
7374
+ "properties": {
7375
+ "name": {
7376
+ "type": ["string", "null"],
7377
+ "description": "Display name, when present (e.g. `Alice Example`)."
7378
+ },
7379
+ "address": {
7380
+ "type": "string",
7381
+ "description": "Bare email address (e.g. `alice@example.com`)."
7382
+ }
7383
+ },
7384
+ "required": ["address"]
7385
+ },
7386
+ "description": "Parsed `To` header addresses."
7387
+ },
7388
+ "in_reply_to": {
7389
+ "type": ["array", "null"],
7390
+ "items": { "type": "string" },
7391
+ "description": "Message-IDs from the `In-Reply-To` header."
7392
+ },
7393
+ "references": {
7394
+ "type": ["array", "null"],
7395
+ "items": { "type": "string" },
7396
+ "description": "Message-IDs from the `References` header."
7397
+ },
7398
+ "attachments": {
7399
+ "type": "array",
7400
+ "items": {
7401
+ "type": "object",
7402
+ "description": "Metadata for one attachment. The bytes are not inline; download\nall attachments for a message as a gzipped tarball via\n`/emails/{id}/attachments.tar.gz`. `sha256` lets you verify a\nspecific part after extraction.\n",
7403
+ "properties": {
7404
+ "filename": { "type": ["string", "null"] },
7405
+ "content_type": { "type": ["string", "null"] },
7406
+ "size_bytes": { "type": "integer" },
7407
+ "sha256": { "type": ["string", "null"] },
7408
+ "part_index": {
7409
+ "type": "integer",
7410
+ "description": "Zero-based index of this part within the message."
7411
+ }
7412
+ },
7413
+ "required": ["size_bytes"]
7414
+ },
7415
+ "description": "Attachment metadata. Empty array when none."
7416
+ },
7417
+ "error": {
7418
+ "type": ["object", "null"],
7419
+ "description": "Present (non-null) only when `status` is `failed`. When\npresent, all three fields are populated, so a consumer can\nbranch on `code` without defensive null checks.\n",
7420
+ "properties": {
7421
+ "code": {
7422
+ "type": "string",
7423
+ "description": "Stable failure code (e.g. `PARSE_FAILED`)."
7424
+ },
7425
+ "message": { "type": "string" },
7426
+ "retryable": { "type": "boolean" }
7427
+ },
7428
+ "required": [
7429
+ "code",
7430
+ "message",
7431
+ "retryable"
7432
+ ]
7433
+ }
7434
+ },
7435
+ "required": ["status"]
7436
+ }],
7437
+ "description": "Parsed MIME content (addresses, threading headers,\nattachment metadata), matching the `email.parsed` object\non the webhook payload so one parser handles both the\nwebhook and this endpoint. The top-level `body_text` /\n`body_html` fields above are the same values as\n`parsed.body_text` / `parsed.body_html`, retained for\nbackward compatibility.\n"
7438
+ },
7439
+ "auth": {
7440
+ "allOf": [{
7441
+ "type": "object",
7442
+ "description": "SPF / DKIM / DMARC verdicts computed at ingest. Mirrors the\n`email.auth` object on the webhook payload. Field names are\ncamelCase to match that payload exactly. For messages received\nbefore auth was recorded, the verdicts default to `none`.\n",
7443
+ "properties": {
7444
+ "spf": {
7445
+ "type": "string",
7446
+ "description": "SPF result (e.g. `pass`, `fail`, `softfail`, `none`)."
7447
+ },
7448
+ "dmarc": {
7449
+ "type": "string",
7450
+ "description": "DMARC result (e.g. `pass`, `fail`, `none`)."
7451
+ },
7452
+ "dmarcPolicy": {
7453
+ "type": ["string", "null"],
7454
+ "description": "Published DMARC policy (`none`, `quarantine`, `reject`)."
7455
+ },
7456
+ "dmarcFromDomain": {
7457
+ "type": ["string", "null"],
7458
+ "description": "The From-header domain DMARC was evaluated against."
7459
+ },
7460
+ "dmarcSpfAligned": { "type": "boolean" },
7461
+ "dmarcDkimAligned": { "type": "boolean" },
7462
+ "dmarcSpfStrict": { "type": ["boolean", "null"] },
7463
+ "dmarcDkimStrict": { "type": ["boolean", "null"] },
7464
+ "dkimSignatures": {
7465
+ "type": "array",
7466
+ "items": {
7467
+ "type": "object",
7468
+ "description": "One DKIM signature found on the message, with its verdict.",
7469
+ "properties": {
7470
+ "domain": { "type": "string" },
7471
+ "selector": { "type": "string" },
7472
+ "result": {
7473
+ "type": "string",
7474
+ "description": "Verification result (e.g. `pass`, `fail`, `none`)."
7475
+ },
7476
+ "aligned": {
7477
+ "type": "boolean",
7478
+ "description": "Whether the signing domain aligns with the From domain (for DMARC)."
7479
+ },
7480
+ "keyBits": { "type": ["integer", "null"] },
7481
+ "algo": { "type": ["string", "null"] }
7482
+ },
7483
+ "required": [
7484
+ "domain",
7485
+ "selector",
7486
+ "result",
7487
+ "aligned"
7488
+ ]
7489
+ }
7490
+ }
7491
+ },
7492
+ "required": [
7493
+ "spf",
7494
+ "dmarc",
7495
+ "dmarcSpfAligned",
7496
+ "dmarcDkimAligned",
7497
+ "dkimSignatures"
7498
+ ]
7499
+ }],
7500
+ "description": "SPF / DKIM / DMARC verdicts computed at ingest, matching\nthe `email.auth` object on the webhook payload. Use these\nto decide how much to trust a message before acting on\ninstructions it contains.\n"
7004
7501
  }
7005
7502
  },
7006
7503
  "required": [
@@ -7014,7 +7511,9 @@ const operationManifest = [
7014
7511
  "webhook_attempt_count",
7015
7512
  "from_email",
7016
7513
  "to_email",
7017
- "replies"
7514
+ "replies",
7515
+ "parsed",
7516
+ "auth"
7018
7517
  ]
7019
7518
  },
7020
7519
  "sdkName": "getEmail",
@@ -7144,7 +7643,12 @@ const operationManifest = [
7144
7643
  null
7145
7644
  ]
7146
7645
  },
7147
- "webhook_attempt_count": { "type": "integer" }
7646
+ "webhook_attempt_count": { "type": "integer" },
7647
+ "thread_id": {
7648
+ "type": ["string", "null"],
7649
+ "format": "uuid",
7650
+ "description": "Conversation thread this message belongs to. Fetch\n`/threads/{thread_id}` for the full ordered thread. NULL on\nmessages received before threading was enabled.\n"
7651
+ }
7148
7652
  },
7149
7653
  "required": [
7150
7654
  "id",
@@ -7405,7 +7909,12 @@ const operationManifest = [
7405
7909
  null
7406
7910
  ]
7407
7911
  },
7408
- "webhook_attempt_count": { "type": "integer" }
7912
+ "webhook_attempt_count": { "type": "integer" },
7913
+ "thread_id": {
7914
+ "type": ["string", "null"],
7915
+ "format": "uuid",
7916
+ "description": "Conversation thread this message belongs to. Fetch\n`/threads/{thread_id}` for the full ordered thread. NULL on\nmessages received before threading was enabled.\n"
7917
+ }
7409
7918
  },
7410
7919
  "required": [
7411
7920
  "id",
@@ -9651,6 +10160,11 @@ const operationManifest = [
9651
10160
  "format": "uuid",
9652
10161
  "description": "Reference to the inbound `emails.id` that this send\nreplied to, when known. Populated when the caller used\n/emails/{id}/reply or when /send-mail's `in_reply_to`\nmatched a stored inbound message_id in the same org.\n"
9653
10162
  },
10163
+ "thread_id": {
10164
+ "type": ["string", "null"],
10165
+ "format": "uuid",
10166
+ "description": "Conversation thread this send belongs to. A reply inherits\nthe thread of the inbound it answers; a fresh send starts a\nnew thread. Fetch `/threads/{thread_id}` for the full\nordered thread (inbound + outbound interleaved). NULL on\ngate-denied sends and on sends created before threading was\nenabled.\n"
10167
+ },
9654
10168
  "queue_id": {
9655
10169
  "type": ["string", "null"],
9656
10170
  "description": "Message identifier assigned by Primitive's outbound\nrelay once the agent accepts the message. Null on\nqueued, gate_denied, and agent_failed rows.\n"
@@ -9935,6 +10449,11 @@ const operationManifest = [
9935
10449
  "format": "uuid",
9936
10450
  "description": "Reference to the inbound `emails.id` that this send\nreplied to, when known. Populated when the caller used\n/emails/{id}/reply or when /send-mail's `in_reply_to`\nmatched a stored inbound message_id in the same org.\n"
9937
10451
  },
10452
+ "thread_id": {
10453
+ "type": ["string", "null"],
10454
+ "format": "uuid",
10455
+ "description": "Conversation thread this send belongs to. A reply inherits\nthe thread of the inbound it answers; a fresh send starts a\nnew thread. Fetch `/threads/{thread_id}` for the full\nordered thread (inbound + outbound interleaved). NULL on\ngate-denied sends and on sends created before threading was\nenabled.\n"
10456
+ },
9938
10457
  "queue_id": {
9939
10458
  "type": ["string", "null"],
9940
10459
  "description": "Message identifier assigned by Primitive's outbound\nrelay once the agent accepts the message. Null on\nqueued, gate_denied, and agent_failed rows.\n"
@@ -10387,6 +10906,101 @@ const operationManifest = [
10387
10906
  "tag": "Sending",
10388
10907
  "tagCommand": "sending"
10389
10908
  },
10909
+ {
10910
+ "binaryResponse": false,
10911
+ "bodyRequired": false,
10912
+ "command": "get-thread",
10913
+ "description": "Returns a conversation thread: its metadata plus the inbound\nand outbound messages that belong to it, interleaved in time\norder (oldest first). A thread spans both received emails and\nyour sends, so an agent can reconstruct an entire back-and-forth\nfrom one call instead of walking reply headers.\n\nEach message carries a `direction` (`inbound` | `outbound`) and\nan `id`; fetch the full message via `/emails/{id}` or\n`/sent-emails/{id}` accordingly. Bodies are omitted here to keep\nthe thread view lightweight.\n\nDiscover a thread id from the `thread_id` field on any email or\nsent-email (list or detail). The message list is capped; compare\n`message_count` against `messages.length` to detect truncation.\n",
10914
+ "hasJsonBody": false,
10915
+ "method": "GET",
10916
+ "operationId": "getThread",
10917
+ "path": "/threads/{id}",
10918
+ "pathParams": [{
10919
+ "description": "Resource UUID",
10920
+ "enum": null,
10921
+ "name": "id",
10922
+ "required": true,
10923
+ "type": "string"
10924
+ }],
10925
+ "queryParams": [],
10926
+ "requestSchema": null,
10927
+ "responseSchema": {
10928
+ "type": "object",
10929
+ "description": "A conversation thread: its metadata plus the inbound and\noutbound messages that belong to it, interleaved oldest-first.\nMembership is the stored `thread_id` on each message. Bodies are\nomitted here to keep the thread view lightweight; fetch\n`/emails/{id}` or `/sent-emails/{id}` for a single message's\nfull content.\n",
10930
+ "properties": {
10931
+ "id": {
10932
+ "type": "string",
10933
+ "format": "uuid"
10934
+ },
10935
+ "subject": {
10936
+ "type": ["string", "null"],
10937
+ "description": "Normalized subject of the thread (Re/Fwd prefixes stripped)."
10938
+ },
10939
+ "root_message_id": {
10940
+ "type": ["string", "null"],
10941
+ "description": "Message-ID of the conversation root, when known."
10942
+ },
10943
+ "message_count": {
10944
+ "type": "integer",
10945
+ "description": "Total messages in the thread. `messages` is capped (most\nrecent first, then re-sorted oldest-first), so\n`message_count > messages.length` signals truncation.\n"
10946
+ },
10947
+ "first_message_at": {
10948
+ "type": ["string", "null"],
10949
+ "format": "date-time"
10950
+ },
10951
+ "last_message_at": {
10952
+ "type": ["string", "null"],
10953
+ "format": "date-time"
10954
+ },
10955
+ "created_at": {
10956
+ "type": "string",
10957
+ "format": "date-time"
10958
+ },
10959
+ "messages": {
10960
+ "type": "array",
10961
+ "items": {
10962
+ "type": "object",
10963
+ "description": "One message in a thread (inbound or outbound).",
10964
+ "properties": {
10965
+ "direction": {
10966
+ "type": "string",
10967
+ "enum": ["inbound", "outbound"],
10968
+ "description": "`inbound` for a received email (`/emails/{id}`), `outbound`\nfor a send (`/sent-emails/{id}`). Use it with `id` to fetch\nfull content from the right endpoint.\n"
10969
+ },
10970
+ "id": {
10971
+ "type": "string",
10972
+ "format": "uuid"
10973
+ },
10974
+ "message_id": { "type": ["string", "null"] },
10975
+ "from": { "type": ["string", "null"] },
10976
+ "to": { "type": ["string", "null"] },
10977
+ "subject": { "type": ["string", "null"] },
10978
+ "status": {
10979
+ "type": ["string", "null"],
10980
+ "description": "Lifecycle status (an EmailStatus or SentEmailStatus value, per `direction`)."
10981
+ },
10982
+ "timestamp": {
10983
+ "type": ["string", "null"],
10984
+ "format": "date-time",
10985
+ "description": "received_at for inbound, created_at for outbound."
10986
+ }
10987
+ },
10988
+ "required": ["direction", "id"]
10989
+ }
10990
+ }
10991
+ },
10992
+ "required": [
10993
+ "id",
10994
+ "message_count",
10995
+ "created_at",
10996
+ "messages"
10997
+ ]
10998
+ },
10999
+ "sdkName": "getThread",
11000
+ "summary": "Get a conversation thread by id",
11001
+ "tag": "Threads",
11002
+ "tagCommand": "threads"
11003
+ },
10390
11004
  {
10391
11005
  "binaryResponse": false,
10392
11006
  "bodyRequired": false,