Documentation Index: Fetch llms.txt first to discover every published page. This page is also available as Markdown at /go-sdk-quickstart-81405d38.md.
Verified · 8/11/2026

Go SDK Quickstart

Install github.com/primitivedotdev/sdks/sdk-go, verify an inbound webhook into a normalized email, and send a reply, all in a single Go program.

Get from zero to a verified inbound email and a sent reply with the Go SDK, github.com/primitivedotdev/sdks/sdk-go, in one program.

Note

Requires Go 1.25 or newer. You'll also need a Primitive API key (prim_test in the examples below) and a webhook signing secret (whsec_...) from your dashboard.

  1. 1

    Install the SDK#

    go get github.com/primitivedotdev/sdks/sdk-go@latest
    

    This pulls in the root primitive package (the high-level email client, x402 payments, and webhook helpers) plus the sibling github.com/primitivedotdev/sdks/sdk-go/api package, the generated HTTP client for the full API surface.

  2. 2

    Set your API key and webhook secret#

    Export both as environment variables so they never land in source control:

    export PRIMITIVE_API_KEY=prim_test
    export PRIMITIVE_WEBHOOK_SECRET=whsec_...
    

    PRIMITIVE_API_KEY authenticates outbound calls (client.Send, client.Reply). PRIMITIVE_WEBHOOK_SECRET verifies that inbound webhook deliveries actually came from Primitive.

  3. 3

    Verify a webhook and reply to it#

    primitive.Receive verifies the Primitive-Signature header, parses the raw body, and returns a normalized ReceivedEmail. Pass that email straight into client.Reply, which derives the recipient, subject (Re: <parent>), and threading headers server-side.

    // main.go
    package main
    
    import (
    	"context"
    	"io"
    	"log"
    	"net/http"
    	"os"
    	"time"
    
    	primitive "github.com/primitivedotdev/sdks/sdk-go"
    )
    
    var client *primitive.Client
    
    func handler(w http.ResponseWriter, r *http.Request) {
    	// Verification runs over the exact request bytes, so read the raw body
    	// before any JSON decoding.
    	body, err := io.ReadAll(r.Body)
    	if err != nil {
    		http.Error(w, "could not read body", http.StatusBadRequest)
    		return
    	}
    
    	headers := map[string]string{
    		"Primitive-Signature": r.Header.Get("Primitive-Signature"),
    		"X-Webhook-Event":     r.Header.Get("X-Webhook-Event"),
    	}
    
    	email, err := primitive.Receive(primitive.HandleWebhookOptions{
    		Body:    body,
    		Headers: headers,
    		Secret:  os.Getenv("PRIMITIVE_WEBHOOK_SECRET"),
    	})
    	if err != nil {
    		log.Printf("invalid webhook: %v", err)
    		http.Error(w, "invalid webhook", http.StatusBadRequest)
    		return
    	}
    
    	ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
    	defer cancel()
    
    	if _, err := client.Reply(ctx, email, primitive.ReplyParams{
    		BodyText: "Thank you for your email.",
    	}); err != nil {
    		log.Printf("reply failed: %v", err)
    		http.Error(w, "reply failed", http.StatusInternalServerError)
    		return
    	}
    
    	w.WriteHeader(http.StatusOK)
    }
    
    func main() {
    	var err error
    	client, err = primitive.NewClient(os.Getenv("PRIMITIVE_API_KEY"))
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	http.HandleFunc("/webhooks/email", handler)
    	log.Println("listening on :8080")
    	log.Fatal(http.ListenAndServe(":8080", nil))
    }
    
  4. 4

    Verify it worked#

    Send a test webhook delivery to /webhooks/email from your Primitive dashboard. On success:

    • The handler returns HTTP 200.
    • Your logs show no invalid webhook or reply failed line.
    • The sender receives a reply with the body "Thank you for your email." threaded under the original message.

    A 400 response means signature verification failed: check that PRIMITIVE_WEBHOOK_SECRET matches the secret shown in your dashboard and that you passed the raw body bytes. A 500 means the reply call failed; inspect the logged error, which is a *primitive.APIError carrying StatusCode, Code, and Message.

Send a new email instead of replying#

If you're sending outbound mail rather than reacting to an inbound webhook, call client.Send directly.

// send.go
package main

import (
	"context"
	"log"
	"os"
	"time"

	primitive "github.com/primitivedotdev/sdks/sdk-go"
)

func main() {
	client, err := primitive.NewClient(os.Getenv("PRIMITIVE_API_KEY"))
	if err != nil {
		log.Fatal(err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 60*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)
}

A successful run logs the sent email's id, its status, and the accepted recipients (here, alice@example.com).

Send and Reply return as soon as Primitive accepts the message by default. Set Wait to a pointer to true (with WaitTimeoutMs, default 30000) when you need the first downstream SMTP outcome instead. See the inbound and outbound email model for the DeliveryStatus values wait mode reports.

Tip

Building in Node.js or Python instead? See the Node.js SDK Quickstart or the Python SDK quickstart. All three SDKs implement the identical inbound/outbound model, so pick by language.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer