{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/go-replying-to-emails","markdown_url":"https://test.abhinandan.one/go-replying-to-emails.md","article":{"id":"5fb187f6-044d-4f85-b764-bb05431d41d2","article_slug":"go-replying-to-emails","parent_article_slug":null,"parent_article_title":null,"kind":"guide","published_at":"2026-08-11T18:54:52.206364+00:00","keywords":["Client.Reply","ReplyParams","primitive.ReplyParams","inbound_not_repliable","SendAttachment Go","reply from-override"],"meta_description":"Client.Reply sends a threaded reply to an inbound email, deriving recipients, subject, and threading headers server-side from the inbound row.","og_image_url":null,"source_file_paths":["sdk-go/client.go","sdk-go/README.md"],"recording_id":null,"replayable":false,"task_name":"Replying to Emails","category":"Go SDK","summary":null,"description":"Use Client.Reply to send a threaded reply to an inbound email, with server-derived recipients and subject, an optional From override, and inline attachments.","content_kind":"repo_page","content_markdown":"`Client.Reply` sends an outbound reply to an inbound [`ReceivedEmail`](email-model), letting the server derive the recipient, `Re:` subject, and threading headers instead of you rebuilding them by hand. Reach for it any time a handler needs to respond to the email it just received.\n\n## What the server derives for you\n\nThe server derives the recipients, the `Re: <parent>` subject, and the `In-Reply-To` / `References` threading headers from the inbound row; you supply only the body and a few optional fields. `Reply` calls `POST /emails/{id}/reply` using the inbound email's ID. Recipients, the `Re: <parent>` subject, and the `In-Reply-To` / `References` threading headers are all derived server-side from that inbound row, you only control the body, an optional `From` override, optional attachments, and the `Wait` flag.\n\n`ReplyParams` intentionally has no `Subject` field. Gmail's Conversation View needs both a `References` match and a normalized-subject match to thread correctly; a custom subject silently breaks threading for part of the recipient population. Use [`Client.Send`](go-sending-emails) if you need full subject control instead.\n\n<Steps>\n\n<Step title=\"Receive and normalize the inbound email\">\n\nVerify and normalize the inbound webhook into a `ReceivedEmail` with `primitive.Receive`, covered in [Receiving and Verifying Webhooks](go-receiving-webhooks).\n\n```go\nimport primitive \"github.com/primitivedotdev/sdks/sdk-go\"\n\nemail, err := primitive.Receive(primitive.HandleWebhookOptions{\n\tBody:    body,\n\tHeaders: headers,\n\tSecret:  \"whsec_...\",\n})\nif err != nil {\n\tlog.Printf(\"invalid webhook: %v\", err)\n\treturn\n}\n```\n\n</Step>\n\n<Step title=\"Construct a client\">\n\n```go\nclient, err := primitive.NewClient(\"prim_test\")\nif err != nil {\n\tlog.Fatal(err)\n}\n```\n\nSee [Client and Configuration](go-client-configuration) for `NewClientWithOptions` and base-URL overrides.\n\n</Step>\n\n<Step title=\"Call Client.Reply\">\n\n```go\nctx := context.Background()\n\nresult, err := client.Reply(ctx, email, primitive.ReplyParams{\n\tBodyText: \"Thank you for your email.\",\n})\nif err != nil {\n\tlog.Printf(\"reply failed: %v\", err)\n\treturn\n}\n\nlog.Println(result.ID, result.Status)\n```\n\n`Reply` requires either `BodyText` or `BodyHTML` (or both); an empty body returns an error before any request is made.\n\n</Step>\n\n</Steps>\n\n## ReplyParams fields\n\n`ReplyParams` carries five fields: `BodyText`, `BodyHTML`, `From`, `Attachments`, and `Wait`.\n\n```go\ntype ReplyParams struct {\n\tBodyText    string\n\tBodyHTML    string\n\tFrom        string\n\tAttachments []SendAttachment\n\tWait        *bool\n}\n```\n\n| Field | Purpose |\n| --- | --- |\n| `BodyText` / `BodyHTML` | The reply body. Provide at least one; both are accepted as siblings. |\n| `From` | Overrides the From header. Defaults server-side to the address that received the inbound email. |\n| `Attachments` | Inline `SendAttachment` values (base64 content). |\n| `Wait` | Pointer to a bool. When true, mirrors [wait mode](email-model) on `Send`. |\n\n## Reply from a different address\n\nSet `ReplyParams.From` to override the From header. `Reply` otherwise defaults it to the inbound recipient, the address that received the email. When your verified outbound domain differs from your inbound domain, pass `From` explicitly:\n\n```go\n_, err = client.Reply(ctx, email, primitive.ReplyParams{\n\tBodyText: \"Thanks for your email.\",\n\tFrom:     \"notifications@outbound.example.com\",\n})\n```\n\n## HTML replies, attachments, and waiting for delivery\n\nSet `BodyHTML` alongside `BodyText`, pass base64 `Attachments`, and point `Wait` at `true` to hold the response open for the delivery outcome, all on the same `ReplyParams`:\n\n```go\nwait := true\n_, err = client.Reply(ctx, email, primitive.ReplyParams{\n\tBodyText: \"Thanks for your email.\",\n\tBodyHTML: \"<p>Thanks for your email.</p>\",\n\tAttachments: []primitive.SendAttachment{\n\t\t{\n\t\t\tFilename:      \"report.txt\",\n\t\t\tContentBase64: \"aGVsbG8=\",\n\t\t},\n\t},\n\tWait: &wait,\n})\n```\n\n`Wait` is a `*bool` (not a bare `bool`) so the SDK can distinguish \"unset\" from \"explicitly false.\" When `Wait` points to `true`, the call holds the HTTP response open until the first downstream SMTP [delivery status](email-model) (`delivered`, `bounced`, `deferred`, or `wait_timeout`), or `WaitTimeoutMs` (default 30000ms) elapses. Set a `context.Context` deadline of 30-60 seconds when waiting, see [Context, Timeouts, and Cancellation](go-context-timeouts).\n\n## Pitfalls\n\n<Warning>\n\nIf the inbound row isn't in a repliable state, the email was rejected, its content was discarded, or no recipient was recorded, the API returns `inbound_not_repliable` (HTTP 422) and `Reply` returns an error. A missing `Message-Id` does **not** block the reply; it only omits the threading headers on the outbound message.\n\n</Warning>\n\n<Warning>\n\nPassing a `Subject` is not supported. If you need to control the subject line, use [`Client.Send`](go-sending-emails) directly instead of `Reply`.\n\n</Warning>\n\n## Return value and errors\n\n`Reply` returns a `SendResult` and an `error`; non-2xx responses come back as a `*primitive.APIError`. The `SendResult` shape is identical to `Send`'s: `ID`, `Status`, `QueueID`, `Accepted`, `Rejected`, `ClientIdempotencyKey`, `RequestID`, `ContentHash`, `IdempotentReplay`, and (when `Wait` was used) `DeliveryStatus`, `SMTPResponseCode`, `SMTPResponseText`.\n\nNon-2xx responses map to a `*primitive.APIError` carrying `StatusCode`, `Code`, `Message`, `RequestID`, `Gates`, and `RetryAfter` (on 429). See [Error Handling](go-error-handling) for the full type and how to distinguish it from a `context.Canceled` / `context.DeadlineExceeded` client-side abort.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Forwarding Emails\" href=\"go-forwarding-emails\">\n\nSend an inbound email to a new recipient with Client.Forward.\n\n</Card>\n\n<Card title=\"Sending Emails\" href=\"go-sending-emails\">\n\nSend new outbound mail with Client.Send and full subject control.\n\n</Card>\n\n<Card title=\"Receiving and Verifying Webhooks\" href=\"go-receiving-webhooks\">\n\nNormalize an inbound webhook into a ReceivedEmail before replying.\n\n</Card>\n\n<Card title=\"Error Handling\" href=\"go-error-handling\">\n\nInspect APIError and distinguish it from client-side context errors.\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/client.go","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Replying+to+Emails&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fgo-replying-to-emails","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}