quolle

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 18 Imported by: 0

README

Quolle Go SDK

Official Go client for the Quolle email API.

Install

go get github.com/Quolle-main/quolle-go

Requires Go 1.21+. No third-party dependencies.

Quick start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Quolle-main/quolle-go"
)

func main() {
	client := quolle.New("qle_...") // or set QUOLLE_API_KEY

	res, err := client.Emails.Send(context.Background(), quolle.SendParams{
		From:    "hello@mail.yourdomain.com",
		To:      quolle.To("customer@example.com"),
		Subject: "Welcome!",
		HTML:    "<h1>Thanks for signing up</h1>",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Queued:", res.ID)
}

Sending

Multiple recipients
client.Emails.Send(ctx, quolle.SendParams{
	From:    "hello@mail.yourdomain.com",
	To:      quolle.ToMany("a@example.com", "b@example.com"),
	Subject: "Announcement",
	HTML:    "<p>Hello everyone</p>",
})
Templates
client.Emails.Send(ctx, quolle.SendParams{
	From:      "hello@mail.yourdomain.com",
	To:        quolle.To("customer@example.com"),
	Template:  "welcome-email",
	Variables: map[string]any{"firstName": "Amaka", "planName": "Starter"},
})
Scheduled send
client.Emails.Send(ctx, quolle.SendParams{
	From:        "hello@mail.yourdomain.com",
	To:          quolle.To("customer@example.com"),
	Subject:     "Your weekly digest",
	HTML:        "<p>Here's what happened this week.</p>",
	ScheduledAt: "2026-12-25T09:00:00.000Z",
})
Idempotency
client.Emails.Send(ctx, params, quolle.WithIdempotencyKey("order_invoice_12345"))
Batch
out, _ := client.Emails.SendBatch(ctx, []quolle.SendParams{
	{From: "hello@mail.yourdomain.com", To: quolle.To("a@example.com"), Subject: "Hi Alice", HTML: "<p>Hi Alice</p>"},
	{From: "hello@mail.yourdomain.com", To: quolle.To("b@example.com"), Subject: "Hi Bob", HTML: "<p>Hi Bob</p>"},
})
fmt.Printf("Queued %d: %v\n", out.Queued, out.IDs)
Attachments

Attach files with the Attachments field. quolle.FileAttachment reads a file and base64-encodes it (guessing the content type). Up to 20 files, 10 MB total.

pdf, err := quolle.FileAttachment("invoice.pdf")
if err != nil {
	log.Fatal(err)
}
client.Emails.Send(ctx, quolle.SendParams{
	From:        "billing@mail.yourdomain.com",
	To:          quolle.To("customer@example.com"),
	Subject:     "Your invoice",
	HTML:        "<p>Invoice attached.</p>",
	Attachments: []quolle.Attachment{pdf},
})

Retrieve & cancel

email, _ := client.Emails.Get(ctx, "a1b2c3d4-...")
fmt.Println(email.Status) // queued | sending | sent | delivered | bounced | failed

client.Emails.Cancel(ctx, "a1b2c3d4-...") // only while status == "scheduled"

Error handling

res, err := client.Emails.Send(ctx, params)
if err != nil {
	var qe *quolle.Error
	if errors.As(err, &qe) {
		fmt.Println(qe.StatusCode) // e.g. 402
		fmt.Println(qe.Message)    // e.g. "Monthly limit reached"
		fmt.Println(qe.Data)       // extra fields, e.g. map[limit:3000]
	}
}

Testing your integration

Send to a reserved test address to simulate any outcome without touching your sending reputation: delivered@test.quolle.com, bounced@test.quolle.com, complained@test.quolle.com, suppressed@test.quolle.com.

Verifying webhooks

Confirm an incoming webhook really came from Quolle with the raw body, the Quolle-Signature header, and your signing secret (whsec_…):

func handler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    if err := quolle.VerifyWebhook(body, r.Header.Get("Quolle-Signature"), "whsec_...", 0); err != nil {
        http.Error(w, "invalid signature", http.StatusBadRequest)
        return
    }
    // signature valid — parse body and handle the event
}

HMAC-SHA256 with a 5-minute timestamp window (replay protection). Pass 0 for the default tolerance.

Automatic retries

Transient failures — HTTP 429 (rate limit) and 5xx, plus network errors — are retried automatically with exponential backoff, honoring the Retry-After header. To avoid double-sending, a POST is only retried on a 5xx/network error when you pass an idempotency key; a 429 is always safe to retry (the request was never processed). Tune with quolle.WithMaxRetries(n) (default 3).

License

MIT

quolle-go

Documentation

Overview

Package quolle is the official Go client for the Quolle email API.

client := quolle.New("qle_...")
res, err := client.Emails.Send(context.Background(), quolle.SendParams{
    From:    "hello@mail.yourdomain.com",
    To:      quolle.To("customer@example.com"),
    Subject: "Welcome!",
    HTML:    "<h1>Thanks for signing up</h1>",
})
if err != nil { log.Fatal(err) }
fmt.Println(res.ID)

Index

Constants

View Source
const DefaultWebhookTolerance = 5 * time.Minute

DefaultWebhookTolerance is the max age of a webhook timestamp before it's rejected as a possible replay.

View Source
const Version = "1.0.0"

Version is the SDK version, sent in the User-Agent header.

Variables

This section is empty.

Functions

func VerifyWebhook

func VerifyWebhook(payload []byte, signatureHeader, secret string, tolerance time.Duration) error

VerifyWebhook checks that a webhook payload really came from Quolle.

Pass the RAW request body, the value of the "Quolle-Signature" header, and your webhook signing secret (whsec_…, shown once when you created the webhook). Returns nil on success, or an error if the header is malformed, the timestamp is outside the tolerance window, or the signature doesn't match.

if err := quolle.VerifyWebhook(body, r.Header.Get("Quolle-Signature"), secret, 0); err != nil {
    http.Error(w, "invalid signature", http.StatusBadRequest)
    return
}

Types

type Attachment

type Attachment struct {
	Filename    string `json:"filename"`
	Content     string `json:"content,omitempty"`
	Path        string `json:"path,omitempty"`
	ContentType string `json:"contentType,omitempty"`
	ContentID   string `json:"contentId,omitempty"`
}

Attachment is a file attached to an email. Provide either Content (the raw file bytes, base64-encoded) or Path (a URL Quolle fetches at send time). Set ContentID to embed the file inline via <img src="cid:the-id">. Up to 20 attachments, 10 MB total.

func FileAttachment

func FileAttachment(path string) (Attachment, error)

FileAttachment reads a file from disk and returns an Attachment with its base64-encoded content and a content type guessed from the extension.

type BatchResult

type BatchResult struct {
	Queued int      `json:"queued"`
	IDs    []string `json:"ids"`
}

BatchResult is returned by SendBatch.

type CancelResult

type CancelResult struct {
	Message string `json:"message"`
}

CancelResult is returned by Cancel.

type Client

type Client struct {

	// Emails exposes the email operations (Send, SendBatch, Get, Cancel).
	Emails *Emails
	// contains filtered or unexported fields
}

Client is the Quolle API client. Create one with New.

func New

func New(apiKey string, opts ...Option) *Client

New creates a Client. If apiKey is empty, it falls back to the QUOLLE_API_KEY environment variable. It panics only if no key can be found.

type Email

type Email struct {
	ID          string `json:"id"`
	From        string `json:"from"`
	To          string `json:"to"`
	Subject     string `json:"subject"`
	Status      string `json:"status"`
	Provider    string `json:"provider"`
	MessageID   string `json:"messageId"`
	OpensCount  int    `json:"opensCount"`
	ClicksCount int    `json:"clicksCount"`
	SentAt      string `json:"sentAt"`
	DeliveredAt string `json:"deliveredAt"`
	BouncedAt   string `json:"bouncedAt"`
	CreatedAt   string `json:"createdAt"`
}

Email is a retrieved email record.

type Emails

type Emails struct {
	// contains filtered or unexported fields
}

Emails provides email operations. Access it via Client.Emails.

func (*Emails) Cancel

func (e *Emails) Cancel(ctx context.Context, id string) (*CancelResult, error)

Cancel cancels a scheduled email that has not yet been sent.

func (*Emails) Get

func (e *Emails) Get(ctx context.Context, id string) (*Email, error)

Get retrieves a single email by ID.

func (*Emails) Send

func (e *Emails) Send(ctx context.Context, params SendParams, opts ...SendOption) (*SendResult, error)

Send queues an email for immediate or scheduled delivery.

func (*Emails) SendBatch

func (e *Emails) SendBatch(ctx context.Context, emails []SendParams, opts ...SendOption) (*BatchResult, error)

SendBatch sends up to 100 emails in one all-or-nothing request.

type Error

type Error struct {
	// StatusCode is the HTTP status code.
	StatusCode int
	// Message is the human-readable error (from the API's "error" field).
	Message string
	// Data holds any extra fields returned alongside "error".
	Data map[string]any
}

Error is returned when the API responds with a non-2xx status.

func (*Error) Error

func (e *Error) Error() string

type Option

type Option func(*Client)

Option customizes a Client.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the API base URL (default https://api.quolle.com).

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient supplies a custom *http.Client (e.g. with a proxy or timeout).

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how many times to retry transient failures (default 3).

type Recipients

type Recipients struct {
	// contains filtered or unexported fields
}

Recipients is the value for the To field: one or many addresses. Use To or ToMany to construct it. It marshals to a JSON string for a single recipient and a JSON array for multiple, matching the API.

func To

func To(addr string) Recipients

To builds a single-recipient value.

func ToMany

func ToMany(addrs ...string) Recipients

ToMany builds a multi-recipient value.

func (Recipients) MarshalJSON

func (r Recipients) MarshalJSON() ([]byte, error)

MarshalJSON emits a bare string for one recipient, an array for many.

type SendOption

type SendOption func(map[string]string)

SendOption customizes a single Send/SendBatch call.

func WithIdempotencyKey

func WithIdempotencyKey(key string) SendOption

WithIdempotencyKey makes a send safe to retry — the same key returns the original result instead of sending twice.

type SendParams

type SendParams struct {
	From        string         `json:"from"`
	To          Recipients     `json:"to"`
	Subject     string         `json:"subject,omitempty"`
	HTML        string         `json:"html,omitempty"`
	Text        string         `json:"text,omitempty"`
	Template    string         `json:"template,omitempty"`
	Variables   map[string]any `json:"variables,omitempty"`
	ReplyTo     string         `json:"replyTo,omitempty"`
	ScheduledAt string         `json:"scheduledAt,omitempty"`
	Attachments []Attachment   `json:"attachments,omitempty"`
	Metadata    map[string]any `json:"metadata,omitempty"`
}

SendParams are the fields for a single send. Provide either HTML (with Subject) or Template (with optional Variables).

type SendResult

type SendResult struct {
	ID          string `json:"id"`
	Message     string `json:"message"`
	Status      string `json:"status,omitempty"`
	ScheduledAt string `json:"scheduledAt,omitempty"`
}

SendResult is returned by Send.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL