Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WildDuck Go SDK

A lightweight and modular Go client for interacting with the WildDuck API. This SDK mirrors the endpoint coverage of wildduck-nodesdk while using idiomatic Go patterns such as context.Context, net/http, and explicit error returns.


Features

  • Modular API surface for authentication, users, addresses, mailboxes, messages, submissions, filters, and autoreplies.
  • WildDuck authentication through the X-Access-Token header.
  • Simple request payloads and query parameters with wildduck.M.
  • Context-aware calls for deadlines, cancellation, and request scoping.
  • Custom HTTP client support for timeouts, tracing, proxies, or tests.
  • Raw byte helpers for message sources and attachments.
  • No third-party runtime dependencies.

Installation

Install the module with go get:

go get github.com/tenforwardab/wildduck-gosdk

Import it in your Go code:

import wildduck "github.com/tenforwardab/wildduck-gosdk"

Usage

Initializing the Client

Create a client with your WildDuck API key and API base URL:

package main

import (
	"context"
	"fmt"
	"log"

	wildduck "github.com/tenforwardab/wildduck-gosdk"
)

func main() {
	ctx := context.Background()
	api := wildduck.New("YOUR_API_KEY", "https://api.example.com")

	users, err := api.Users.List(ctx, wildduck.M{"limit": 20})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(users["results"])
}

Custom HTTP Client

Use WithHTTPClient when you need custom transport settings:

httpClient := &http.Client{
	Timeout: 10 * time.Second,
}

api := wildduck.New(
	"YOUR_API_KEY",
	"https://api.example.com",
	wildduck.WithHTTPClient(httpClient),
)

API Modules

The root client exposes these services:

  • api.Authentication
  • api.Users
  • api.Addresses
  • api.Mailboxes
  • api.Messages
  • api.Submission
  • api.Filters
  • api.Autoreplies

All methods take context.Context. JSON request bodies and query strings are passed as wildduck.M, which is an alias for map[string]any.


Authentication

Pre-authentication

Check whether a username exists and can authenticate for a scope:

response, err := api.Authentication.PreAuth(ctx, "user@example.com", "imap", wildduck.M{
	"sess": "session-id",
	"ip":   "192.0.2.10",
})
if err != nil {
	log.Fatal(err)
}

fmt.Println(response["success"])

Authenticate a User

Authenticate a user and optionally request a temporary access token:

auth, err := api.Authentication.Authenticate(ctx, "user@example.com", "password123", wildduck.M{
	"scope":    "imap",
	"protocol": "imap",
	"token":    true,
})
if err != nil {
	log.Fatal(err)
}

fmt.Println(auth)

Invalidate a Token

Invalidate the current access token:

result, err := api.Authentication.InvalidateToken(ctx)
if err != nil {
	log.Fatal(err)
}

fmt.Println(result["success"])

Users

List Users

users, err := api.Users.List(ctx, wildduck.M{
	"query":    "example",
	"limit":    50,
	"metaData": true,
})

Create a User

created, err := api.Users.Create(ctx, wildduck.M{
	"username": "alice",
	"password": "securePassword123",
	"name":     "Alice Example",
	"address":  "alice@example.com",
	"quota":    1073741824,
	"tags":     []string{"customer", "active"},
})
if err != nil {
	log.Fatal(err)
}

fmt.Println(created["id"])

Resolve, Fetch, Update, and Delete

resolved, err := api.Users.Resolve(ctx, "alice", nil)
userID := resolved["id"].(string)

user, err := api.Users.Get(ctx, userID, wildduck.M{"metaData": true})

updated, err := api.Users.Update(ctx, userID, wildduck.M{
	"name":     "Alice Admin",
	"spamLevel": 80,
})

deleted, err := api.Users.Delete(ctx, userID, wildduck.M{
	"deleteAfter": "2026-12-31T23:59:59Z",
})

_, _, _, _ = user, updated, deleted, err

Account Operations

_, err = api.Users.Logout(ctx, userID, wildduck.M{
	"reason": "Administrative logout",
})

quota, err := api.Users.RecalculateQuota(ctx, userID, nil)

task, err := api.Users.RecalculateAllQuota(ctx, nil)

password, err := api.Users.ResetPassword(ctx, userID, wildduck.M{
	"validAfter": "2026-06-16T10:00:00Z",
})

recovery, err := api.Users.GetRecoveryInfo(ctx, userID, nil)

restored, err := api.Users.CancelDeletion(ctx, userID, nil)

_, _, _, _, _ = quota, task, password, recovery, restored

Change Stream URL

The Go SDK returns the stream URL so the caller can choose an SSE implementation:

streamURL, err := api.Users.ChangeStreamURL(userID, wildduck.M{
	"LastEventID": "previous-event-id",
})

Addresses

List and Create Addresses

addresses, err := api.Addresses.List(ctx, wildduck.M{
	"query": "example.com",
	"limit": 20,
})

address, err := api.Addresses.Create(ctx, userID, wildduck.M{
	"address": "alice@example.com",
	"name":    "Alice Example",
	"main":    true,
	"tags":    []string{"primary"},
})

_, _ = addresses, address

User Addresses

userAddresses, err := api.Addresses.ListForUser(ctx, userID, wildduck.M{
	"metaData": true,
})

addressInfo, err := api.Addresses.Get(ctx, userID, "ADDRESS_ID", nil)

_, err = api.Addresses.Update(ctx, userID, "ADDRESS_ID", wildduck.M{
	"name": "Updated Identity",
	"main": true,
})

_, err = api.Addresses.Delete(ctx, userID, "ADDRESS_ID", nil)

_, _ = userAddresses, addressInfo

Forwarded Addresses

forwarded, err := api.Addresses.CreateForwarded(ctx, wildduck.M{
	"address": "sales@example.com",
	"name":    "Sales",
	"targets": []string{"sales-team@example.net"},
})

_, err = api.Addresses.UpdateForwarded(ctx, "ADDRESS_ID", wildduck.M{
	"targets": []string{"new-target@example.net"},
})

info, err := api.Addresses.GetForwarded(ctx, "ADDRESS_ID", nil)

_, err = api.Addresses.DeleteForwarded(ctx, "ADDRESS_ID", nil)

_, _ = forwarded, info

Register, Resolve, and Domain Rename

register, err := api.Addresses.ListRegister(ctx, userID, wildduck.M{
	"query": "ali",
	"limit": 25,
})

resolvedAddress, err := api.Addresses.Resolve(ctx, "alice@example.com", wildduck.M{
	"allowWildcard": true,
})

renamed, err := api.Addresses.RenameDomain(ctx, wildduck.M{
	"oldDomain": "old.example.com",
	"newDomain": "new.example.com",
})

_, _, _ = register, resolvedAddress, renamed

Mailboxes

List Mailboxes

mailboxes, err := api.Mailboxes.List(ctx, userID, wildduck.M{
	"counters": true,
	"sizes":    true,
})

Create, Get, Update, and Delete

mailbox, err := api.Mailboxes.Create(ctx, userID, wildduck.M{
	"path":      "Projects/Go",
	"retention": 0,
})

mailboxInfo, err := api.Mailboxes.Get(ctx, userID, "MAILBOX_ID", nil)

_, err = api.Mailboxes.Update(ctx, userID, "MAILBOX_ID", wildduck.M{
	"path":       "Projects/WildDuck",
	"subscribed": true,
})

_, err = api.Mailboxes.Delete(ctx, userID, "MAILBOX_ID", nil)

_, _, _ = mailboxes, mailbox, mailboxInfo

Messages

List and Search

messages, err := api.Messages.List(ctx, userID, "MAILBOX_ID", wildduck.M{
	"unseen": true,
	"limit":  25,
	"order":  "desc",
})

search, err := api.Messages.Search(ctx, userID, wildduck.M{
	"query":      "invoice",
	"searchable": true,
	"limit":      10,
})

_, _ = messages, search

Upload a Message

uploaded, err := api.Messages.Upload(ctx, userID, "MAILBOX_ID", wildduck.M{
	"from": wildduck.M{"name": "Alice", "address": "alice@example.com"},
	"to": []wildduck.M{
		{"name": "Bob", "address": "bob@example.com"},
	},
	"subject": "Stored message",
	"text":    "This message is uploaded to a mailbox.",
})

Update and Delete Messages

updated, err := api.Messages.UpdateMany(ctx, userID, "MAILBOX_ID", wildduck.M{
	"message": "1,2,3",
	"seen":    true,
})

single, err := api.Messages.Update(ctx, userID, "MAILBOX_ID", "123", wildduck.M{
	"flagged": true,
})

_, err = api.Messages.Delete(ctx, userID, "MAILBOX_ID", 123, nil)

_, err = api.Messages.DeleteMany(ctx, userID, "MAILBOX_ID", wildduck.M{
	"async": true,
})

_, _, _ = uploaded, updated, single

Fetch Message Details, Source, and Attachments

messageInfo, err := api.Messages.Get(ctx, userID, "MAILBOX_ID", 123, wildduck.M{
	"markAsSeen": true,
})

source, err := api.Messages.Source(ctx, userID, "MAILBOX_ID", 123, nil)

attachment, err := api.Messages.Attachment(ctx, userID, "MAILBOX_ID", 123, "ATTACHMENT_ID", false)

_, _, _ = messageInfo, source, attachment

For large message sources or attachments, use the streaming variants to avoid buffering the full response in memory:

sourceBody, err := api.Messages.SourceStream(ctx, userID, "MAILBOX_ID", 123, nil)
if err != nil {
	log.Fatal(err)
}
defer sourceBody.Close()

attachmentBody, err := api.Messages.AttachmentStream(ctx, userID, "MAILBOX_ID", 123, "ATTACHMENT_ID", false)
if err != nil {
	log.Fatal(err)
}
defer attachmentBody.Close()

Forward, Submit Drafts, and Outbound Queue

forwardedMessage, err := api.Messages.Forward(ctx, userID, "MAILBOX_ID", 123, wildduck.M{
	"addresses": []string{"target@example.net"},
})

submittedDraft, err := api.Messages.SubmitDraft(ctx, userID, "MAILBOX_ID", 456, wildduck.M{
	"deleteFiles": true,
})

_, err = api.Messages.DeleteOutbound(ctx, userID, "QUEUE_ID", nil)

_, _ = forwardedMessage, submittedDraft

Message Submission

Send or upload a message through the WildDuck submission endpoint:

submitted, err := api.Submission.Submit(ctx, userID, wildduck.M{
	"mailbox": "MAILBOX_ID",
	"from":    wildduck.M{"name": "Alice", "address": "alice@example.com"},
	"to": []wildduck.M{
		{"name": "Bob", "address": "bob@example.com"},
	},
	"subject": "Hello from WildDuck Go SDK",
	"text":    "This was sent through the WildDuck API.",
	"headers": []wildduck.M{
		{"key": "X-App", "value": "wildduck-gosdk"},
	},
})
if err != nil {
	log.Fatal(err)
}

fmt.Println(submitted["message"])

Filters

List Filters

allFilters, err := api.Filters.ListAll(ctx, wildduck.M{
	"metaData": true,
	"limit":    50,
})

userFilters, err := api.Filters.ListForUser(ctx, userID, wildduck.M{
	"metaData": true,
})

_, _ = allFilters, userFilters

Create, Get, Update, and Delete

filter, err := api.Filters.Create(ctx, userID, wildduck.M{
	"name": "Invoices",
	"query": wildduck.M{
		"subject": "Invoice",
	},
	"action": wildduck.M{
		"seen":    true,
		"mailbox": "MAILBOX_ID",
	},
})

filterInfo, err := api.Filters.Get(ctx, userID, "FILTER_ID", nil)

_, err = api.Filters.Update(ctx, userID, "FILTER_ID", wildduck.M{
	"name":     "Updated invoices",
	"query":    wildduck.M{"subject": "Invoice"},
	"action":   wildduck.M{"seen": true},
	"disabled": false,
})

_, err = api.Filters.Delete(ctx, userID, "FILTER_ID", nil)

_, _ = filter, filterInfo

Autoreplies

Get, Update, and Delete

autoreply, err := api.Autoreplies.Get(ctx, userID, nil)

updatedAutoreply, err := api.Autoreplies.Update(ctx, userID, wildduck.M{
	"status":  true,
	"name":    "Alice Example",
	"subject": "Out of office",
	"text":    "I am currently out of office.",
	"html":    "<p>I am currently out of office.</p>",
	"start":   "2026-07-01T00:00:00Z",
	"end":     "2026-07-15T00:00:00Z",
})

_, err = api.Autoreplies.Delete(ctx, userID, nil)

_, _ = autoreply, updatedAutoreply

Error Handling

Network and JSON errors are returned directly. Non-2xx HTTP responses are returned as *wildduck.APIError and include the HTTP status code, raw response body, and parsed Code/Message fields when WildDuck returns a JSON error payload.

_, err := api.Users.List(ctx, nil)
if err != nil {
	if apiErr, ok := err.(*wildduck.APIError); ok {
		fmt.Printf("WildDuck returned HTTP %d: %s %s\n", apiErr.StatusCode, apiErr.Code, apiErr.Message)
		return
	}

	log.Fatal(err)
}

Example Project Structure

my-project/
├── cmd/
│   └── app/
│       └── main.go
├── internal/
│   └── mail/
│       └── wildduck.go
├── go.mod
└── go.sum

Example internal/mail/wildduck.go:

package mail

import wildduck "github.com/tenforwardab/wildduck-gosdk"

func NewWildDuck(apiKey, apiURL string) *wildduck.Client {
	return wildduck.New(apiKey, apiURL)
}

Notes

This SDK is intentionally thin. WildDuck response payloads can vary by endpoint and API version, so methods return wildduck.M for JSON responses. For stricter application code, unmarshal or map the returned values into your own domain structs at the application boundary.


Contributing

Contributions are welcome. Please keep changes focused, add tests for client behavior where practical, and keep the SDK aligned with WildDuck API endpoints.


License

This project is licensed under the EUPL v1.2.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages