{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/node-sdk-payloads","markdown_url":"https://test.abhinandan.one/node-sdk-payloads.md","article":{"id":"d88f9cbe-1037-4e34-a665-9f257cc776bf","article_slug":"node-sdk-payloads","parent_article_slug":null,"parent_article_title":null,"kind":"guide","published_at":"2026-08-11T18:54:54.485146+00:00","keywords":["pushFile","pushBytes","pullFile","SendPayloadReference","sendAttachment","Primitive Payloads"],"meta_description":"pushFile, pushBytes, and pullFile stream E2E-encrypted attachment objects in bounded memory and reference them on send instead of inlining bytes.","og_image_url":null,"source_file_paths":["sdk-node/src/api/index.ts"],"recording_id":null,"replayable":false,"task_name":"Primitive Payloads: Streaming Large Attachments","category":"Node.js SDK","summary":null,"description":"Upload and download large, content-addressed, end-to-end-encrypted attachment objects in bounded memory, then attach them to email by reference instead of inlining bytes.","content_kind":"repo_page","content_markdown":"Use Primitive Payloads, the streaming client for large, content-addressed, end-to-end-encrypted attachment objects, when a file is too big to inline on `send`, `reply`, or `forward`. Inline attachments (base64 content) cover anything under the inline cap (~30 MiB of combined raw bytes); Payloads uploads and downloads in bounded memory instead.\n\nA finalized Payloads object is identified by `PushResult.merkleRoot`, a 64-character lowercase-hex Merkle root, and decrypted with `PushResult.cek`, a hex content-encryption key the caller holds. Reference the object on a send instead of inlining its bytes.\n\n<Tip>\n\nFor anything under the inline cap, skip Payloads entirely and pass `attachments` directly to [`client.send` / `client.reply` / `client.forward`](node-sdk-sending-email). Reach for `pushFile` / `pushBytes` only once you're above that threshold, or use `client.sendAttachment`, which picks inline-vs-reference for you based on size.\n\n</Tip>\n\n## Upload and attach a large file\n\nUpload the file with `pushFile`, then deliver it by reference through `payloadAttachments` on `client.send`. Two steps, no inline bytes.\n\n<Steps>\n\n<Step title=\"Push the file to Payloads storage\">\n\nStream the file from disk with `pushFile` (use `pushBytes` for in-memory data instead). Both are exported from the payloads module of `@primitivedotdev/sdk`.\n\n```typescript\n// upload.ts\nimport { pushFile } from \"@primitivedotdev/sdk/payloads\";\n\nconst pushed = await pushFile({\n  path: \"./recording.mp4\",\n  apiKey: process.env.PRIMITIVE_API_KEY!,\n});\n\nconsole.log(pushed.merkleRoot); // 64-char lowercase hex\nconsole.log(pushed.cek);        // hex content-encryption key\n```\n\n`pushed` is a `PushResult`: `merkleRoot` is the content-addressed id for the finalized object, and `cek` is the hex content-encryption key the recipient needs to decrypt it. Keep both; you need them to reference or download the object later.\n\n</Step>\n\n<Step title=\"Reference the uploaded object as an attachment\">\n\nBuild a `SendPayloadReference` from the push result and pass it on `send` via `payloadAttachments` instead of `attachments`. Only one payload attachment is supported per send in v1.\n\n```typescript\n// send.ts\nimport primitive from \"@primitivedotdev/sdk\";\n\nconst client = primitive.client({\n  apiKey: process.env.PRIMITIVE_API_KEY!,\n});\n\nawait client.send({\n  from: \"Support <support@example.com>\",\n  to: \"alice@example.com\",\n  subject: \"Hello\",\n  bodyText: \"Your recording is attached.\",\n  payloadAttachments: [\n    {\n      root: pushed.merkleRoot,\n      filename: \"recording.mp4\",\n      contentType: \"video/mp4\",\n      cek: pushed.cek,\n    },\n  ],\n});\n```\n\nThe SDK converts `cek` (hex, matching `PushResult.cek` verbatim) to the base64url encoding the wire format expects, so the entire SDK surface, push, pull, and reference, stays hex to the caller.\n\n</Step>\n\n</Steps>\n\n<Tip>\n\nSkip the manual push-then-reference dance with `client.sendAttachment(...)`. Pass the normal send fields plus a single `attachment` (`content` for in-memory bytes, or `path` to stream a file from disk), and the SDK sends it inline when it's at or below `inlineThreshold` (defaults to 25 MiB, the server's inline/offload threshold) and uploads-then-references it otherwise. Provide exactly one of `content` or `path`.\n\n</Tip>\n\n## Download a payload object\n\nUse `pullFile` to stream a Payloads object back down and decrypt it, passing the same `merkleRoot` and `cek` the push returned.\n\n```typescript\n// download.ts\nimport { pullFile } from \"@primitivedotdev/sdk/payloads\";\n\nawait pullFile({\n  root: pushed.merkleRoot,\n  cek: pushed.cek,\n  path: \"./downloaded-recording.mp4\",\n  apiKey: process.env.PRIMITIVE_API_KEY!,\n});\n```\n\n`pullFile` streams and decrypts in bounded memory, so a multi-gigabyte object never has to be resident.\n\n<Warning>\n\nThe CEK is client-held: the SDK's push/pull/reference surface carries it, so if you lose the `cek` for a `merkleRoot` you cannot decrypt the object. Persist both together (for example alongside the email record) if you need to re-download later.\n\n</Warning>\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Sending, Replying, and Forwarding Email\" href=\"node-sdk-sending-email\">\n\nSend inline attachments directly for anything under the inline cap.\n\n</Card>\n\n<Card title=\"Receiving Inbound Email\" href=\"node-sdk-receiving-email\">\n\nSee how ReceivedEmail exposes attachments and download references.\n\n</Card>\n\n<Card title=\"Request Options and Idempotency\" href=\"node-sdk-request-options\">\n\nApply per-call timeouts and idempotency keys to send calls that reference payloads.\n\n</Card>\n\n<Card title=\"Payloads Command: Streaming Large Attachments from the CLI\" href=\"cli-payloads-command\">\n\nUpload and download the same encrypted objects from the terminal.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/sdk-node/src/api/index.ts","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Primitive+Payloads%3A+Streaming+Large+Attachments&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fnode-sdk-payloads","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}