{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/go-sending-emails","markdown_url":"https://test.abhinandan.one/go-sending-emails.md","article":{"id":"0df85781-b1a8-460f-98c9-9ee3a75b2922","article_slug":"go-sending-emails","parent_article_slug":null,"parent_article_title":null,"kind":"guide","published_at":"2026-08-11T18:54:51.56226+00:00","keywords":["Client.Send","SendParams","Go SDK send email","Wait WaitTimeoutMs","IdempotencyKey","delivery status Go"],"meta_description":"Client.Send in the Go SDK dispatches outbound mail synchronously by default, with an optional Wait flag for SMTP delivery confirmation.","og_image_url":null,"source_file_paths":["sdk-go/client.go","sdk-go/README.md"],"recording_id":null,"replayable":false,"task_name":"Sending Emails (Go SDK)","category":"Go SDK","summary":null,"description":"Send outbound emails with Client.Send, choosing between the default fast-return behavior and wait mode for delivery confirmation, with idempotent retries via IdempotencyKey.","content_kind":"repo_page","content_markdown":"`Client.Send` sends a new outbound email through the Primitive API. Use it whenever your Go service needs to originate mail rather than reply to an inbound message (for replies, see [Replying to Emails](go-replying-to-emails)).\n\n## Send a basic email\n\n<Steps>\n\n<Step title=\"Construct the client\">\n\nBuild a [`Client`](go-client-configuration) with your API key:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\tprimitive \"github.com/primitivedotdev/sdks/sdk-go\"\n)\n\nfunc main() {\n\tclient, err := primitive.NewClient(\"prim_test\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t_ = client\n}\n```\n\n</Step>\n\n<Step title=\"Call Client.Send with a context deadline\">\n\n`Send` takes `ctx context.Context` as its first argument and a `primitive.SendParams` struct. Give the context a deadline: see [Context, Timeouts, and Cancellation](go-context-timeouts) for how Go SDK methods use `context.Context` instead of a separate request-options type.\n\n```go\nctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)\ndefer cancel()\n\nresult, err := client.Send(ctx, primitive.SendParams{\n\tFrom:     \"Support <support@example.com>\",\n\tTo:       \"alice@example.com\",\n\tSubject:  \"Hello\",\n\tBodyText: \"Hi there\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\n\nlog.Println(result.ID, result.Status, result.Accepted)\n```\n\n</Step>\n\n<Step title=\"Read the result\">\n\n`Send` returns a `primitive.SendResult`:\n\n```go\ntype SendResult struct {\n\tID                   string\n\tStatus               primitiveapi.SentEmailStatus\n\tQueueID              primitiveapi.NilString\n\tAccepted             []string\n\tRejected             []string\n\tClientIdempotencyKey string\n\tRequestID            string\n\tContentHash          string\n\tIdempotentReplay     bool\n\tDeliveryStatus       primitiveapi.OptDeliveryStatus\n\tSMTPResponseCode     primitiveapi.OptNilInt\n\tSMTPResponseText     primitiveapi.OptString\n}\n```\n\n`IdempotentReplay` is `true` when the response replays a previously-recorded send keyed by `ClientIdempotencyKey` (same key, same canonical payload), false on a fresh send and on gate-denied responses. `DeliveryStatus`, `SMTPResponseCode`, and `SMTPResponseText` are only populated when you use [wait mode](#wait-for-delivery-confirmation).\n\n</Step>\n\n</Steps>\n\n`SendParams` requires `From`, `To`, `Subject`, and at least one of `BodyText` or `BodyHTML`. Validation runs client-side before any network call:\n\n- `From` and `To` must be 3-998 and 3-320 characters respectively.\n- `To` must parse as a valid email address (bare or `Display Name <addr>` form).\n- `Subject` must be non-empty.\n- `WaitTimeoutMs`, if set, must be between 1000 and 30000.\n\nA validation failure returns a Go `error` immediately, with no request sent.\n\n## Wait for delivery confirmation\n\nBy default, `Send` returns as soon as Primitive accepts the message, it does not wait for the receiving mail server to respond. Pass `Wait: &wait` (a `*bool`, since `false` and unset are distinct in the generated request) to hold the request open until the first downstream SMTP delivery outcome:\n\n```go\nwait := true\n\nresult, err := client.Send(ctx, primitive.SendParams{\n\tFrom:          \"Support <support@example.com>\",\n\tTo:            \"alice@example.com\",\n\tSubject:       \"Hello\",\n\tBodyText:      \"Hi there\",\n\tWait:          &wait,\n\tWaitTimeoutMs: 5000,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\n\nlog.Println(result.DeliveryStatus, result.SMTPResponseCode)\n```\n\n`WaitTimeoutMs` bounds how long the server waits for an outcome before returning `wait_timeout`; it defaults to 30000 ms when omitted and must be between 1000 and 30000 ms. With `Wait` set, `Send` and `Reply` keep the HTTP request open until Primitive's downstream SMTP transaction completes, so give the request a `context.Context` deadline long enough for SMTP delivery.\n\nSee [Inbound and Outbound Email Model](email-model) for the full wait-mode and delivery-status contract shared across every SDK. The terminal `DeliveryStatus` values are:\n\n| Status | Meaning |\n|---|---|\n| `delivered` | Accepted by the receiving MTA |\n| `bounced` | Rejected by the receiving MTA (the HTTP response is still 200 OK) |\n| `deferred` | Temporary failure; the receiving MTA may retry |\n| `wait_timeout` | No outcome observed in time, treat as \"outcome unknown,\" the send may still complete after the response returns |\n\n<Tip>\n\n`Forward` (see [Forwarding Emails](go-forwarding-emails)) has no `Wait` option and always returns as soon as the API accepts the message.\n\n</Tip>\n\n## Dedupe retries with IdempotencyKey\n\nSet `IdempotencyKey` on `SendParams` to make retries safe. Use a unique key per logical send, reusing a key returns the original response from the first send instead of sending a duplicate:\n\n```go\nresult, err := client.Send(ctx, primitive.SendParams{\n\tFrom:           \"Support <support@example.com>\",\n\tTo:             \"alice@example.com\",\n\tSubject:        \"Hello\",\n\tBodyText:       \"Hi there\",\n\tIdempotencyKey: \"customer-key-abc123\",\n})\n```\n\nCheck `result.IdempotentReplay` to tell a fresh send apart from a replayed one. `ForwardParams` also accepts `IdempotencyKey` for the same reason.\n\n<Warning>\n\nA `WaitTimeoutMs` outside 1000-30000 fails client-side validation before any request is sent. Set it only alongside `Wait: &wait`; it has no effect otherwise.\n\n</Warning>\n\n## Threading a new send\n\nPass `Thread` to link a new outbound email into an existing conversation via `In-Reply-To` and `References` headers:\n\n```go\nresult, err := client.Send(ctx, primitive.SendParams{\n\tFrom:     \"Support <support@example.com>\",\n\tTo:       \"alice@example.com\",\n\tSubject:  \"Re: Hello\",\n\tBodyText: \"Following up\",\n\tThread: &primitive.SendThread{\n\t\tInReplyTo:  \"<parent-message-id@example.com>\",\n\t\tReferences: []string{\"<root@example.com>\", \"<parent-message-id@example.com>\"},\n\t},\n})\n```\n\nFor replying to an inbound `ReceivedEmail` directly (server-derived recipients, subject, and threading), use `Client.Reply` instead, see [Replying to Emails](go-replying-to-emails).\n\n## Errors\n\nA non-2xx response or a mapping failure returns a `*primitive.APIError`, carrying `StatusCode`, `Code`, `Message`, `RetryAfter`, `Gates`, `RequestID`, and `Details`. See [Error Handling](go-error-handling) for the full type and how to inspect it with `errors.As`.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Replying to Emails\" href=\"go-replying-to-emails\">\n\nReply to an inbound email with server-derived recipients, subject, and threading.\n\n</Card>\n\n<Card title=\"Forwarding Emails\" href=\"go-forwarding-emails\">\n\nForward an inbound email to a new recipient with Client.Forward.\n\n</Card>\n\n<Card title=\"Context, Timeouts, and Cancellation\" href=\"go-context-timeouts\">\n\nUse context.Context deadlines and cancellation on every network call.\n\n</Card>\n\n<Card title=\"Error Handling\" href=\"go-error-handling\">\n\nInspect APIError and other typed errors to handle failures precisely.\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+Sending+Emails+%28Go+SDK%29&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fgo-sending-emails","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}