Skip to content

lunogram/go-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lunogram Go SDK

Go Reference

Go client library for the Lunogram Client API. Manage user profiles, organizations, events, inbox messages, and scheduled resources from any Go application.

Installation

go get github.com/lunogram/go-sdk

Requires Go 1.22 or later.

Project scoping

Every Client API request is scoped to a single Lunogram project. You supply the project ID (a UUID) once, when constructing the client, and the SDK injects it into every request path automatically — you never pass it per call. Requests are issued under /api/client/projects/{projectID}/....

NewClient validates the project ID and returns an error if it is empty or not a valid UUID.

Quick start

package main

import (
	"context"
	"fmt"
	"log"

	lunogram "github.com/lunogram/go-sdk"
)

func main() {
	// The project ID (a UUID) scopes every request. It is required.
	client, err := lunogram.NewClient("your-api-key", "your-project-id")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	user, err := client.UpsertUser(ctx, &lunogram.UpsertUserRequest{
		Identifier: []lunogram.ExternalID{
			{ExternalID: "user_123"},
		},
		Email: lunogram.String("jane@example.com"),
		Data: map[string]any{
			"first_name": "Jane",
			"plan":       "pro",
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Upserted user:", user.ID)
}

Client options

All options are passed after the required apiKey and projectID arguments, and NewClient returns (*Client, error).

// Custom base URL (e.g. for self-hosted or staging environments)
client, err := lunogram.NewClient(apiKey, projectID, lunogram.WithBaseURL("https://outreach.internal"))

// Custom HTTP client
client, err := lunogram.NewClient(apiKey, projectID, lunogram.WithHTTPClient(&http.Client{
	Timeout:   10 * time.Second,
	Transport: myTransport,
}))

// Custom timeout (applies to the default HTTP client)
client, err := lunogram.NewClient(apiKey, projectID, lunogram.WithTimeout(10*time.Second))

Users

Upsert a user

Create a new user or update an existing one. Users are matched by their external identifiers.

user, err := client.UpsertUser(ctx, &lunogram.UpsertUserRequest{
	Identifier: []lunogram.ExternalID{
		{ExternalID: "user_123"},
	},
	Email:    lunogram.String("jane@example.com"),
	Phone:    lunogram.String("+31612345678"),
	Timezone: lunogram.String("Europe/Amsterdam"),
	Locale:   lunogram.String("nl-NL"),
	Data: map[string]any{
		"first_name":    "Jane",
		"plan":          "enterprise",
	},
})

Delete a user

err := client.DeleteUser(ctx, &lunogram.DeleteUserRequest{
	Identifier: []lunogram.ExternalID{
		{ExternalID: "user_123"},
	},
})

Post user events

Send events that can trigger journeys or update virtual lists. Events are processed asynchronously.

err := client.PostUserEvents(ctx, []lunogram.Event{
	{
		Name: "purchase_completed",
		Identifier: []lunogram.ExternalID{
			{ExternalID: "user_123"},
		},
		Data: map[string]any{
			"product_id": "prod_789",
			"amount":     99.99,
		},
	},
})

You can also target events at users matching specific data attributes instead of an identifier:

err := client.PostUserEvents(ctx, []lunogram.Event{
	{
		Name:  "feature_announcement",
		Match: map[string]any{"plan": "enterprise"},
		Data:  map[string]any{"feature": "advanced_analytics"},
	},
})

Organizations

Upsert an organization

org, err := client.UpsertOrganization(ctx, &lunogram.UpsertOrganizationRequest{
	Identifier: []lunogram.ExternalID{
		{ExternalID: "org_456"},
	},
	Name: lunogram.String("Acme Corp"),
	Data: map[string]any{
		"industry": "technology",
		"size":     "enterprise",
	},
})

Delete an organization

Deleting an organization also removes all its user memberships.

err := client.DeleteOrganization(ctx, &lunogram.DeleteOrganizationRequest{
	Identifier: []lunogram.ExternalID{
		{ExternalID: "org_456"},
	},
})

Add a user to an organization

err := client.AddOrganizationUser(ctx, &lunogram.OrganizationUserRequest{
	Organization: lunogram.OrganizationRef{
		Identifier: []lunogram.ExternalID{{ExternalID: "org_456"}},
	},
	User: lunogram.UserRef{
		Identifier: []lunogram.ExternalID{{ExternalID: "user_123"}},
	},
	Data: map[string]any{
		"role":       "admin",
		"department": "engineering",
	},
})

Remove a user from an organization

err := client.RemoveOrganizationUser(ctx, &lunogram.RemoveOrganizationUserRequest{
	Organization: lunogram.OrganizationRef{
		Identifier: []lunogram.ExternalID{{ExternalID: "org_456"}},
	},
	User: lunogram.UserRef{
		Identifier: []lunogram.ExternalID{{ExternalID: "user_123"}},
	},
})

Post organization events

Events are processed asynchronously and can trigger journeys for all users in the organization.

err := client.PostOrganizationEvents(ctx, []lunogram.OrganizationEvent{
	{
		Name: "subscription_upgraded",
		Identifier: []lunogram.ExternalID{
			{ExternalID: "org_456"},
		},
		Data: map[string]any{
			"plan":  "enterprise",
			"seats": 100,
		},
	},
})

Inbox

Inbox messages are delivered to users or organizations on a specific channel (email, sms, push, or inbox). Creating, reading, and archiving messages are processed asynchronously; querying and counting return immediately.

Create user inbox messages

Identifier is the message's own external identifier, used for idempotency and traceability; Target identifies the recipient.

err := client.PostUserInboxMessages(ctx, []lunogram.InboxMessageCreate{
	{
		Target:     []lunogram.ExternalID{{ExternalID: "user_123"}},
		Identifier: lunogram.ExternalID{ExternalID: "msg_welcome_1"},
		Channel:    lunogram.ChannelInbox,
		Content:    json.RawMessage(`{"title": "Welcome!", "body": "Thanks for joining."}`),
		Tags:       []string{"onboarding"},
	},
})

Query the user inbox

Source, ExternalID, and Channel are required; the remaining fields are optional filters.

list, err := client.GetUserInbox(ctx, &lunogram.InboxQuery{
	Source:     "default",
	ExternalID: "user_123",
	Channel:    lunogram.ChannelInbox,
	Status:     &[]lunogram.InboxStatus{lunogram.InboxStatusUnread}[0],
})
for _, msg := range list.Results {
	fmt.Println(msg.ID, msg.Priority)
}

Count user inbox messages

count, err := client.GetUserInboxCount(ctx, &lunogram.InboxCountQuery{
	Source:     "default",
	ExternalID: "user_123",
	Channel:    lunogram.ChannelInbox,
})
fmt.Printf("%d unread of %d total\n", count.Unread, count.Total)

Mark user messages read or archived

err := client.MarkUserInboxRead(ctx, []lunogram.InboxMessageRef{
	{
		Target:    []lunogram.ExternalID{{ExternalID: "user_123"}},
		MessageID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
	},
})

err = client.MarkUserInboxArchived(ctx, []lunogram.InboxMessageRef{
	{
		Target:    []lunogram.ExternalID{{ExternalID: "user_123"}},
		MessageID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
	},
})

Organization inbox

The organization inbox mirrors the user inbox. Create, query, count, and mark messages with the organization-scoped methods:

err := client.PostOrganizationInboxMessages(ctx, []lunogram.OrganizationInboxMessageCreate{
	{
		Target:     []lunogram.ExternalID{{ExternalID: "org_456"}},
		Identifier: lunogram.ExternalID{ExternalID: "msg_renewal_1"},
		Channel:    lunogram.ChannelInbox,
		Content:    json.RawMessage(`{"title": "Contract renewal"}`),
	},
})

list, err := client.GetOrganizationInbox(ctx, &lunogram.InboxQuery{
	Source:     "default",
	ExternalID: "org_456",
	Channel:    lunogram.ChannelInbox,
})

count, err := client.GetOrganizationInboxCount(ctx, &lunogram.InboxCountQuery{
	Source:     "default",
	ExternalID: "org_456",
	Channel:    lunogram.ChannelInbox,
})

err = client.MarkOrganizationInboxRead(ctx, []lunogram.OrganizationInboxMessageRef{
	{
		Target:    []lunogram.ExternalID{{ExternalID: "org_456"}},
		MessageID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
	},
})

err = client.MarkOrganizationInboxArchived(ctx, []lunogram.OrganizationInboxMessageRef{
	{
		Target:    []lunogram.ExternalID{{ExternalID: "org_456"}},
		MessageID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
	},
})

Devices and push

Register a device's push subscription for a user, and fetch the project's VAPID public key for Web Push:

os := lunogram.DeviceOSWeb
err := client.RegisterDevice(ctx, &lunogram.DeviceRegistration{
	Identifier: []lunogram.ExternalID{{ExternalID: "user_123"}},
	DeviceID:   "user_123_web",
	OS:         &os,
	Config: lunogram.DeviceConfig{
		Endpoint: lunogram.String("https://push.example.com/subscription"),
		Keys: &lunogram.DeviceKeys{
			P256dh: "BKey-p256dh-value",
			Auth:   "auth-secret",
		},
	},
})

key, err := client.GetVapidPublicKey(ctx)
// key.PublicKey is the project's VAPID public key

Sessions

Mint a short-lived session token for an end user, server-side, so the client can call the Client API directly. The session's permissions are defined by the session auth method (policy) identified by authMethodID:

token, err := client.CreateSession(ctx, "auth-method-uuid", &lunogram.CreateSessionRequest{
	UserID: "user_123",
})
// token.Token is a bearer token for the Client API; token.ExpiresAt is its expiry

Scheduled resources

Scheduled resources trigger journey entrance recalculation at a specific time or on a recurring interval.

User scheduled

// Single schedule
accepted, err := client.UpsertUserScheduled(ctx, &lunogram.UpsertUserScheduledRequest{
	Name: "trial_end",
	Identifier: []lunogram.ExternalID{
		{ExternalID: "user_123"},
	},
	ScheduledAt: &trialEnd, // time.Time
	Data: map[string]any{
		"plan":   "pro",
		"amount": 29.99,
	},
})

// Recurring schedule
accepted, err := client.UpsertUserScheduled(ctx, &lunogram.UpsertUserScheduledRequest{
	Name: "weekly_digest",
	Identifier: []lunogram.ExternalID{
		{ExternalID: "user_123"},
	},
	Interval: lunogram.String("168h"), // weekly
})

// Delete scheduled resource
err := client.DeleteUserScheduled(ctx, &lunogram.DeleteUserScheduledRequest{
	Name: "trial_end",
	Identifier: []lunogram.ExternalID{
		{ExternalID: "user_123"},
	},
})

Organization scheduled

accepted, err := client.UpsertOrganizationScheduled(ctx, &lunogram.UpsertOrganizationScheduledRequest{
	Name: "contract_renewal",
	Identifier: []lunogram.ExternalID{
		{ExternalID: "org_456"},
	},
	ScheduledAt: &renewalDate,
	Data: map[string]any{
		"contract_type": "enterprise",
		"seats":         100,
	},
})

err := client.DeleteOrganizationScheduled(ctx, &lunogram.DeleteOrganizationScheduledRequest{
	Name: "contract_renewal",
	Identifier: []lunogram.ExternalID{
		{ExternalID: "org_456"},
	},
})

Error handling

All methods return an *lunogram.APIError when the API responds with a non-2xx status code. You can inspect it for details:

user, err := client.UpsertUser(ctx, req)
if err != nil {
	var apiErr *lunogram.APIError
	if errors.As(err, &apiErr) {
		fmt.Printf("API error %d: %s — %s\n", apiErr.StatusCode, apiErr.Title, apiErr.Detail)
	}
	return err
}

External identifiers

Many requests accept one or more external identifiers. The Source field defaults to "default" when omitted.

// Simple identifier (source defaults to "default")
lunogram.ExternalID{ExternalID: "user_123"}

// Explicit source
lunogram.ExternalID{Source: "stripe", ExternalID: "cus_abc123"}

// With metadata
lunogram.ExternalID{
	Source:     "hubspot",
	ExternalID: "contact_789",
	Metadata:   map[string]any{"synced": true},
}

License

See LICENSE for details.

About

No description, website, or topics provided.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors