---
title: "Replying to Emails"
canonical: "https://test.abhinandan.one/go-replying-to-emails"
markdown_url: "https://test.abhinandan.one/go-replying-to-emails.md"
publisher: "Primitive SDKs"
kind: "guide"
content_type: "reference"
category: "Go SDK"
description: "Client.Reply sends a threaded reply to an inbound email, deriving recipients, subject, and threading headers server-side from the inbound row."
keywords: ["Client.Reply", "ReplyParams", "primitive.ReplyParams", "inbound_not_repliable", "SendAttachment Go", "reply from-override"]
last_modified: "2026-08-11T18:54:52.373583+00:00"
published_at: "2026-08-11T18:54:52.206364+00:00"
source_files:
  - "sdk-go/client.go"
  - "sdk-go/README.md"
sections:
  - {anchor: "what-the-server-derives-for-you", title: "What the server derives for you"}
  - {anchor: "step-receive-and-normalize-the-inbound-email", title: "Receive and normalize the inbound email"}
  - {anchor: "step-construct-a-client", title: "Construct a client"}
  - {anchor: "step-call-clientreply", title: "Call Client.Reply"}
  - {anchor: "replyparams-fields", title: "ReplyParams fields"}
  - {anchor: "reply-from-a-different-address", title: "Reply from a different address"}
  - {anchor: "html-replies-attachments-and-waiting-for-delivery", title: "HTML replies, attachments, and waiting for delivery"}
  - {anchor: "pitfalls", title: "Pitfalls"}
  - {anchor: "return-value-and-errors", title: "Return value and errors"}
  - {anchor: "next-steps", title: "Next steps"}
---

> Documentation index: https://test.abhinandan.one/llms.txt

# Replying to Emails

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.

`Client.Reply` sends an outbound reply to an inbound [`ReceivedEmail`](https://test.abhinandan.one/email-model.md), 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.

## What the server derives for you

The 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.

`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`](https://test.abhinandan.one/go-sending-emails.md) if you need full subject control instead.

### 1. Receive and normalize the inbound email

Verify and normalize the inbound webhook into a `ReceivedEmail` with `primitive.Receive`, covered in [Receiving and Verifying Webhooks](https://test.abhinandan.one/go-receiving-webhooks.md).

```go
import primitive "github.com/primitivedotdev/sdks/sdk-go"

email, err := primitive.Receive(primitive.HandleWebhookOptions{
	Body:    body,
	Headers: headers,
	Secret:  "whsec_...",
})
if err != nil {
	log.Printf("invalid webhook: %v", err)
	return
}
```

### 2. Construct a client

```go
client, err := primitive.NewClient("prim_test")
if err != nil {
	log.Fatal(err)
}
```

See [Client and Configuration](https://test.abhinandan.one/go-client-configuration.md) for `NewClientWithOptions` and base-URL overrides.

### 3. Call Client.Reply

```go
ctx := context.Background()

result, err := client.Reply(ctx, email, primitive.ReplyParams{
	BodyText: "Thank you for your email.",
})
if err != nil {
	log.Printf("reply failed: %v", err)
	return
}

log.Println(result.ID, result.Status)
```

`Reply` requires either `BodyText` or `BodyHTML` (or both); an empty body returns an error before any request is made.

## ReplyParams fields

`ReplyParams` carries five fields: `BodyText`, `BodyHTML`, `From`, `Attachments`, and `Wait`.

```go
type ReplyParams struct {
	BodyText    string
	BodyHTML    string
	From        string
	Attachments []SendAttachment
	Wait        *bool
}
```

| Field | Purpose |
| --- | --- |
| `BodyText` / `BodyHTML` | The reply body. Provide at least one; both are accepted as siblings. |
| `From` | Overrides the From header. Defaults server-side to the address that received the inbound email. |
| `Attachments` | Inline `SendAttachment` values (base64 content). |
| `Wait` | Pointer to a bool. When true, mirrors [wait mode](https://test.abhinandan.one/email-model.md) on `Send`. |

## Reply from a different address

Set `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:

```go
_, err = client.Reply(ctx, email, primitive.ReplyParams{
	BodyText: "Thanks for your email.",
	From:     "notifications@outbound.example.com",
})
```

## HTML replies, attachments, and waiting for delivery

Set `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`:

```go
wait := true
_, err = client.Reply(ctx, email, primitive.ReplyParams{
	BodyText: "Thanks for your email.",
	BodyHTML: "<p>Thanks for your email.</p>",
	Attachments: []primitive.SendAttachment{
		{
			Filename:      "report.txt",
			ContentBase64: "aGVsbG8=",
		},
	},
	Wait: &wait,
})
```

`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](https://test.abhinandan.one/email-model.md) (`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](https://test.abhinandan.one/go-context-timeouts.md).

## Pitfalls

> **Warning:** If 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.

> **Warning:** Passing a `Subject` is not supported. If you need to control the subject line, use [`Client.Send`](https://test.abhinandan.one/go-sending-emails.md) directly instead of `Reply`.

## Return value and errors

`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`.

Non-2xx responses map to a `*primitive.APIError` carrying `StatusCode`, `Code`, `Message`, `RequestID`, `Gates`, and `RetryAfter` (on 429). See [Error Handling](https://test.abhinandan.one/go-error-handling.md) for the full type and how to distinguish it from a `context.Canceled` / `context.DeadlineExceeded` client-side abort.
