{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/go-raw-email-downloads","markdown_url":"https://test.abhinandan.one/go-raw-email-downloads.md","article":{"id":"49f4f533-811a-49c1-ab61-faefb63a5740","article_slug":"go-raw-email-downloads","parent_article_slug":null,"parent_article_title":null,"kind":"guide","published_at":"2026-08-11T18:54:56.376067+00:00","keywords":["DecodeRawEmail","VerifyRawEmailDownload","IsRawIncluded","IsDownloadExpired","GetDownloadTimeRemaining","email.content.raw"],"meta_description":"DecodeRawEmail decodes and SHA-256-verifies inline raw MIME bytes from an EmailReceivedEvent in the Go SDK, with VerifyRawEmailDownload for downloaded content.","og_image_url":null,"source_file_paths":["sdk-go/webhook.go"],"recording_id":null,"replayable":false,"task_name":"Raw Email and Attachment Downloads","category":"Go SDK","summary":null,"description":"Decode inline raw email bytes with SHA-256 verification, or check whether a download URL for large content has expired, using the Go SDK's raw-email helpers.","content_kind":"repo_page","content_markdown":"Every `email.received` event carries the raw MIME source of the inbound message, either inlined as base64 or as a time-limited download URL when it's too large to inline. Use these helpers when you need the original bytes: verifying a DKIM signature yourself, archiving the source `.eml`, or re-parsing headers a normalized field doesn't expose.\n\nFor most handlers you don't need this at all, `primitive.Receive(...)` already gives you a [`ReceivedEmail`](go-receiving-webhooks) with the fields you need. Reach for the raw-email helpers only when you need the bytes themselves.\n\n## Check whether raw content is inline\n\n`primitive.IsRawIncluded(event)`, which reads `email.content.raw.included` off an `email.received` event, returns true when the raw MIME source is inlined and false when it must be downloaded.\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\tprimitive \"github.com/primitivedotdev/sdks/sdk-go\"\n)\n\nfunc inspect(event any) {\n\tincluded, err := primitive.IsRawIncluded(event)\n\tif err != nil {\n\t\t// malformed payload: email.content.raw.included is missing\n\t\tlog.Fatal(err)\n\t}\n\n\tif included {\n\t\traw, err := primitive.DecodeRawEmail(event)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\t_ = raw\n\t\treturn\n\t}\n\t// must download instead, see \"Download large raw content\" below\n}\n```\n\n`IsRawIncluded` returns a `*primitive.WebhookPayloadError` (code `PAYLOAD_WRONG_TYPE`) if `email.content.raw.included` is missing from the payload, which only happens if you hand it something other than a well-formed `EmailReceivedEvent`.\n\n## Decode inline raw content\n\n`primitive.DecodeRawEmail(event)` base64-decodes `email.content.raw.data` and, by default, verifies the bytes against `email.content.raw.sha256`, returning the original MIME source as `[]byte`.\n\n<Steps>\n\n<Step title=\"Confirm the content is inlined\">\n\n`DecodeRawEmail` only works when `email.content.raw.included` is `true`. If it's `false`, it returns a `*primitive.RawEmailDecodeError` with code `NOT_INCLUDED`, whose message reports the raw size, the inline threshold, and the download URL. Check `IsRawIncluded` first if you're not sure.\n\n</Step>\n\n<Step title=\"Decode and verify in one call\">\n\n```go\nraw, err := primitive.DecodeRawEmail(event)\nif err != nil {\n    log.Fatal(err)\n}\n// raw is []byte: the original MIME source\n```\n\nBy default `DecodeRawEmail` verifies the decoded bytes against `email.content.raw.sha256` and fails closed on a mismatch. This catches truncated or corrupted payloads before you act on bad data.\n\n</Step>\n\n<Step title=\"Skip verification only when you have a reason to\">\n\nPass `false` as the second argument to skip the hash check:\n\n```go\nraw, err := primitive.DecodeRawEmail(event, false)\n```\n\n</Step>\n\n</Steps>\n\n### Expected errors\n\n| Code | Cause |\n|---|---|\n| `NOT_INCLUDED` | `email.content.raw.included` is `false`; the raw source must be downloaded instead. The error message includes the download URL. |\n| `INVALID_BASE64` | `email.content.raw.data` failed strict base64 decoding. |\n| `HASH_MISMATCH` | The decoded bytes' SHA-256 doesn't match `email.content.raw.sha256` (only checked when verification is enabled). |\n\n<Warning>\n\n`DecodeRawEmail` assumes a well-formed event from `primitive.Receive(...)` or `primitive.HandleWebhookEvent(...)`. Passing a hand-constructed event with `raw.included: true` but no `raw.data` produces undefined behavior, not a clean error.\n\n</Warning>\n\n## Download large raw content\n\nWhen `email.content.raw.included` is `false`, the raw source exceeded the inline threshold and must be fetched from `email.content.download.url`, then checked with `primitive.VerifyRawEmailDownload(downloaded, event)`.\n\nFetch the URL from the event's `email.content.download.url` field with a standard HTTP client, then verify the bytes:\n\n```go\npackage main\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\n\tprimitive \"github.com/primitivedotdev/sdks/sdk-go\"\n)\n\nfunc download(event any, downloadURL string) []byte {\n\tresp, err := http.Get(downloadURL)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tdownloaded, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tverified, err := primitive.VerifyRawEmailDownload(downloaded, event)\n\tif err != nil {\n\t\t// *primitive.RawEmailDecodeError with code HASH_MISMATCH\n\t\tlog.Fatal(err)\n\t}\n\t// verified == downloaded, but only returned after a passing SHA-256 check\n\treturn verified\n}\n```\n\n`VerifyRawEmailDownload` always checks the hash, there's no skip-verification option, since a downloaded payload has more transport surface (proxies, retries, partial reads) to corrupt it than an inline base64 field.\n\n<Tip>\n\nCall `IsRawIncluded` before deciding whether to decode or download, rather than calling `DecodeRawEmail` and matching on the `NOT_INCLUDED` error. Branching on the boolean keeps the download URL read on the typed event instead of parsed out of an error message.\n\n</Tip>\n\n## Check download URL expiry\n\n`primitive.IsDownloadExpired(event)` compares `email.content.download.expires_at` against now and returns true once the download URL has expired.\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\tprimitive \"github.com/primitivedotdev/sdks/sdk-go\"\n)\n\nfunc checkExpiry(event any) {\n\texpired, err := primitive.IsDownloadExpired(event)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif expired {\n\t\t// the URL in email.content.download.url no longer works\n\t\tlog.Println(\"download URL expired\")\n\t}\n}\n```\n\nTo decide whether there's enough time left for a slow download, use `GetDownloadTimeRemaining`, which returns milliseconds remaining (`0` if already expired):\n\n```go\nremainingMs, err := primitive.GetDownloadTimeRemaining(event)\nif err != nil {\n    log.Fatal(err)\n}\nif remainingMs < 60_000 {\n    // less than a minute left — fetch now or treat as expired\n}\n```\n\nBoth helpers accept an optional second argument (unix milliseconds) to override \"now,\" which is useful in tests:\n\n```go\nexpired, _ := primitive.IsDownloadExpired(event, fixedNowMillis)\nremainingMs, _ := primitive.GetDownloadTimeRemaining(event, fixedNowMillis)\n```\n\nBoth return a `*primitive.WebhookPayloadError` (code `PAYLOAD_WRONG_TYPE`) if `email.content.download.expires_at` is missing or isn't a valid RFC 3339 timestamp.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Receiving and Verifying Webhooks\" href=\"go-receiving-webhooks\">\n\nGet the normalized ReceivedEmail and verified EmailReceivedEvent this page's helpers operate on.\n\n</Card>\n\n<Card title=\"Error Handling\" href=\"go-error-handling\">\n\nLook up RawEmailDecodeError and the other typed error kinds the Go SDK raises.\n\n</Card>\n\n<Card title=\"Webhook Payload Schema Validation\" href=\"go-webhook-schema-validation\">\n\nValidate a raw webhook payload against the EmailReceivedEvent JSON Schema before decoding its content.\n\n</Card>\n\n<Card title=\"Webhook Event Types\" href=\"go-webhook-event-types\">\n\nSee where email.content.raw and email.content.download fit in the full EmailReceivedEvent shape.\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-go/webhook.go","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Raw+Email+and+Attachment+Downloads&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fgo-raw-email-downloads","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}