Sending Emails (Go SDK)
Send outbound emails with Client.Send, choosing between the default fast-return behavior and wait mode for delivery confirmation, with idempotent retries via IdempotencyKey.
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).
Send a basic email#
- 1
- 2
Call Client.Send with a context deadline#
Sendtakesctx context.Contextas its first argument and aprimitive.SendParamsstruct. Give the context a deadline: see Context, Timeouts, and Cancellation for how Go SDK methods usecontext.Contextinstead of a separate request-options type.ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() result, err := client.Send(ctx, primitive.SendParams{ From: "Support <support@example.com>", To: "alice@example.com", Subject: "Hello", BodyText: "Hi there", }) if err != nil { log.Fatal(err) } log.Println(result.ID, result.Status, result.Accepted) - 3
Read the result#
Sendreturns aprimitive.SendResult:type SendResult struct { ID string Status primitiveapi.SentEmailStatus QueueID primitiveapi.NilString Accepted []string Rejected []string ClientIdempotencyKey string RequestID string ContentHash string IdempotentReplay bool DeliveryStatus primitiveapi.OptDeliveryStatus SMTPResponseCode primitiveapi.OptNilInt SMTPResponseText primitiveapi.OptString }IdempotentReplayistruewhen the response replays a previously-recorded send keyed byClientIdempotencyKey(same key, same canonical payload), false on a fresh send and on gate-denied responses.DeliveryStatus,SMTPResponseCode, andSMTPResponseTextare only populated when you use wait mode.
SendParams requires From, To, Subject, and at least one of BodyText or BodyHTML. Validation runs client-side before any network call:
FromandTomust be 3-998 and 3-320 characters respectively.Tomust parse as a valid email address (bare orDisplay Name <addr>form).Subjectmust be non-empty.WaitTimeoutMs, if set, must be between 1000 and 30000.
A validation failure returns a Go error immediately, with no request sent.
Wait for delivery confirmation#
By 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:
wait := true
result, err := client.Send(ctx, primitive.SendParams{
From: "Support <support@example.com>",
To: "alice@example.com",
Subject: "Hello",
BodyText: "Hi there",
Wait: &wait,
WaitTimeoutMs: 5000,
})
if err != nil {
log.Fatal(err)
}
log.Println(result.DeliveryStatus, result.SMTPResponseCode)
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.
See Inbound and Outbound Email Model for the full wait-mode and delivery-status contract shared across every SDK. The terminal DeliveryStatus values are:
| Status | Meaning |
|---|---|
delivered | Accepted by the receiving MTA |
bounced | Rejected by the receiving MTA (the HTTP response is still 200 OK) |
deferred | Temporary failure; the receiving MTA may retry |
wait_timeout | No outcome observed in time, treat as "outcome unknown," the send may still complete after the response returns |
Forward (see Forwarding Emails) has no Wait option and always returns as soon as the API accepts the message.
Dedupe retries with IdempotencyKey#
Set 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:
result, err := client.Send(ctx, primitive.SendParams{
From: "Support <support@example.com>",
To: "alice@example.com",
Subject: "Hello",
BodyText: "Hi there",
IdempotencyKey: "customer-key-abc123",
})
Check result.IdempotentReplay to tell a fresh send apart from a replayed one. ForwardParams also accepts IdempotencyKey for the same reason.
A WaitTimeoutMs outside 1000-30000 fails client-side validation before any request is sent. Set it only alongside Wait: &wait; it has no effect otherwise.
Threading a new send#
Pass Thread to link a new outbound email into an existing conversation via In-Reply-To and References headers:
result, err := client.Send(ctx, primitive.SendParams{
From: "Support <support@example.com>",
To: "alice@example.com",
Subject: "Re: Hello",
BodyText: "Following up",
Thread: &primitive.SendThread{
InReplyTo: "<parent-message-id@example.com>",
References: []string{"<root@example.com>", "<parent-message-id@example.com>"},
},
})
For replying to an inbound ReceivedEmail directly (server-derived recipients, subject, and threading), use Client.Reply instead, see Replying to Emails.
Errors#
A non-2xx response or a mapping failure returns a *primitive.APIError, carrying StatusCode, Code, Message, RetryAfter, Gates, RequestID, and Details. See Error Handling for the full type and how to inspect it with errors.As.
Next steps#
Reply to an inbound email with server-derived recipients, subject, and threading.
Forwarding EmailsForward an inbound email to a new recipient with Client.Forward.
Context, Timeouts, and CancellationUse context.Context deadlines and cancellation on every network call.
Error HandlingInspect APIError and other typed errors to handle failures precisely.
Was this page helpful?