Raw Email and Attachment Downloads
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.
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.
For most handlers you don't need this at all, primitive.Receive(...) already gives you a ReceivedEmail with the fields you need. Reach for the raw-email helpers only when you need the bytes themselves.
Check whether raw content is inline#
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.
package main
import (
"log"
primitive "github.com/primitivedotdev/sdks/sdk-go"
)
func inspect(event any) {
included, err := primitive.IsRawIncluded(event)
if err != nil {
// malformed payload: email.content.raw.included is missing
log.Fatal(err)
}
if included {
raw, err := primitive.DecodeRawEmail(event)
if err != nil {
log.Fatal(err)
}
_ = raw
return
}
// must download instead, see "Download large raw content" below
}
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.
Decode inline raw content#
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.
- 1
Confirm the content is inlined#
DecodeRawEmailonly works whenemail.content.raw.includedistrue. If it'sfalse, it returns a*primitive.RawEmailDecodeErrorwith codeNOT_INCLUDED, whose message reports the raw size, the inline threshold, and the download URL. CheckIsRawIncludedfirst if you're not sure. - 2
Decode and verify in one call#
raw, err := primitive.DecodeRawEmail(event) if err != nil { log.Fatal(err) } // raw is []byte: the original MIME sourceBy default
DecodeRawEmailverifies the decoded bytes againstemail.content.raw.sha256and fails closed on a mismatch. This catches truncated or corrupted payloads before you act on bad data. - 3
Skip verification only when you have a reason to#
Pass
falseas the second argument to skip the hash check:raw, err := primitive.DecodeRawEmail(event, false)
Expected errors#
| Code | Cause |
|---|---|
NOT_INCLUDED | email.content.raw.included is false; the raw source must be downloaded instead. The error message includes the download URL. |
INVALID_BASE64 | email.content.raw.data failed strict base64 decoding. |
HASH_MISMATCH | The decoded bytes' SHA-256 doesn't match email.content.raw.sha256 (only checked when verification is enabled). |
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.
Download large raw content#
When 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).
Fetch the URL from the event's email.content.download.url field with a standard HTTP client, then verify the bytes:
package main
import (
"io"
"log"
"net/http"
primitive "github.com/primitivedotdev/sdks/sdk-go"
)
func download(event any, downloadURL string) []byte {
resp, err := http.Get(downloadURL)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
downloaded, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
verified, err := primitive.VerifyRawEmailDownload(downloaded, event)
if err != nil {
// *primitive.RawEmailDecodeError with code HASH_MISMATCH
log.Fatal(err)
}
// verified == downloaded, but only returned after a passing SHA-256 check
return verified
}
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.
Call 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.
Check download URL expiry#
primitive.IsDownloadExpired(event) compares email.content.download.expires_at against now and returns true once the download URL has expired.
package main
import (
"log"
primitive "github.com/primitivedotdev/sdks/sdk-go"
)
func checkExpiry(event any) {
expired, err := primitive.IsDownloadExpired(event)
if err != nil {
log.Fatal(err)
}
if expired {
// the URL in email.content.download.url no longer works
log.Println("download URL expired")
}
}
To decide whether there's enough time left for a slow download, use GetDownloadTimeRemaining, which returns milliseconds remaining (0 if already expired):
remainingMs, err := primitive.GetDownloadTimeRemaining(event)
if err != nil {
log.Fatal(err)
}
if remainingMs < 60_000 {
// less than a minute left — fetch now or treat as expired
}
Both helpers accept an optional second argument (unix milliseconds) to override "now," which is useful in tests:
expired, _ := primitive.IsDownloadExpired(event, fixedNowMillis)
remainingMs, _ := primitive.GetDownloadTimeRemaining(event, fixedNowMillis)
Both return a *primitive.WebhookPayloadError (code PAYLOAD_WRONG_TYPE) if email.content.download.expires_at is missing or isn't a valid RFC 3339 timestamp.
Next steps#
Get the normalized ReceivedEmail and verified EmailReceivedEvent this page's helpers operate on.
Error HandlingLook up RawEmailDecodeError and the other typed error kinds the Go SDK raises.
Webhook Payload Schema ValidationValidate a raw webhook payload against the EmailReceivedEvent JSON Schema before decoding its content.
Webhook Event TypesSee where email.content.raw and email.content.download fit in the full EmailReceivedEvent shape.
Was this page helpful?