Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<p>
<a href="#quick-start">Quick start</a> •
<a href="#migrating-from-the-legacy-v2-client">Migrate from v2</a> •
<a href="#why-intercom-go">Why intercom-go?</a> •
<a href="#everyday-api">Examples</a> •
<a href="#api-coverage">API coverage</a> •
Expand Down Expand Up @@ -85,6 +86,14 @@ client options:
client, err := intercom.NewClient("access-token", intercom.WithRegion(intercom.EU))
```

## Migrating from the legacy v2 client

`intercom-go` is an independent, community-maintained SDK rather than a drop-in
replacement for `gopkg.in/intercom/intercom-go.v2`. It uses the current Intercom
API model and requires a `context.Context` on each service call. The
[migration guide](docs/migrating-from-intercom-go-v2.md) maps common workflows
and calls out the deliberate API differences.

## Everyday API

### Retrieve and search contacts
Expand Down Expand Up @@ -229,6 +238,9 @@ internal under [`internal/generated/intercom`](internal/generated/intercom).

- [`examples/identify_admin`](examples/identify_admin)
- [`examples/search_contacts`](examples/search_contacts)
- [`examples/list_conversations`](examples/list_conversations)
- [`examples/observe_rate_limits`](examples/observe_rate_limits)
- [`examples/verify_webhook`](examples/verify_webhook)

## Support and Security

Expand Down
Binary file added docs/assets/social-preview-background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions docs/assets/social-preview-overlay.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions docs/launch-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Launch Checklist

This checklist prepares the public `v0.2.2` release and retains the proposed
community announcements for explicit approval. Do not submit either draft
without approval of its final text, target, links, and account identity.

## Repository settings

- [x] Repository description: `Modern, production-ready Go SDK for Intercom API 2.15 (unofficial, community-maintained).`
- [x] Homepage: <https://pkg.go.dev/github.com/uffejaeger/intercom-go>
- [x] Topics: `api-client`, `go`, `golang`, `intercom`, `intercom-api`, `openapi`, `sdk`.
- [ ] Upload [`docs/assets/social-preview.png`](assets/social-preview.png), a 1280×640 PNG under 1 MB, in GitHub's **Settings → General → Social preview**.

## Release checks

- [x] Run `make pre-push`.
- [ ] Merge the release-preparation pull request into `main`.
- [ ] Publish immutable tag and GitHub release `v0.2.2`.
- [ ] Verify `GOPROXY=proxy.golang.org go list -m github.com/uffejaeger/intercom-go@v0.2.2`.
- [ ] Verify <https://pkg.go.dev/github.com/uffejaeger/intercom-go> shows `v0.2.2`.

Installation command:

```sh
go get github.com/uffejaeger/intercom-go@v0.2.2
```

Release summary:

> v0.2.2 adds a migration guide from the legacy v2 client and runnable examples
> for verified webhooks, conversation iteration, and response/rate-limit
> observability. It makes no public Go API changes.

## Draft: Go Forum Releases

**Title:** `intercom-go v0.2.2 — a community-maintained Go SDK for Intercom API 2.15`

> I released `intercom-go` v0.2.2, an unofficial, community-maintained Go SDK
> for Intercom API 2.15.
>
> It keeps generated OpenAPI code internal while exposing idiomatic services,
> opt-in conservative retries, regional endpoints, request/rate-limit metadata,
> verified webhooks, and local HTTP test support.
>
> Install: `go get github.com/uffejaeger/intercom-go@v0.2.2`
>
> The release includes a migration guide for
> `gopkg.in/intercom/intercom-go.v2` and runnable webhook, conversation, and
> observability examples: <https://github.com/uffejaeger/intercom-go>
>
> I would especially value feedback from teams replacing the legacy client:
> which workflow or migration helper would make evaluation easier?

## Draft: Intercom Community

**Title:** `Community Go SDK for Intercom API 2.15 — looking for migration feedback`

> I maintain `intercom-go`, an unofficial, community-maintained Go SDK for
> Intercom API 2.15. It is not affiliated with or endorsed by Intercom.
>
> The project offers typed services, optional conservative retries, regional
> endpoints, response/rate-limit metadata, verified webhooks, and local HTTP
> test support. The latest release also includes a migration guide for the
> legacy `gopkg.in/intercom/intercom-go.v2` client:
> <https://github.com/uffejaeger/intercom-go/blob/main/docs/migrating-from-intercom-go-v2.md>
>
> If you are using Go with Intercom, which legacy-client workflow would you
> want documented or supported first?

## Approval gate

Before posting either draft, confirm the exact final text, destination URL,
account identity, and every linked page with the repository owner. Do not
cross-post or submit to newsletters, curated lists, Reddit, or Intercom
documentation without separate approval.
116 changes: 116 additions & 0 deletions docs/migrating-from-intercom-go-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Migrating From `intercom-go.v2`

This SDK is an independent, community-maintained alternative to
[`gopkg.in/intercom/intercom-go.v2`](https://github.com/intercom/intercom-go).
It is not a drop-in replacement: its public API is shaped around the current
Intercom API and requires a `context.Context` for every request.

Start a migration on a branch, upgrade one workflow at a time, and retain the
legacy client until its replacement has been exercised against a test
workspace. Never run both clients with the same request path in production
unless the operation is safe to duplicate.

## Install and construct a client

Replace the legacy module:

```sh
go get github.com/uffejaeger/intercom-go@v0.2.2
```

```go
// Before
import intercom "gopkg.in/intercom/intercom-go.v2"

legacy := intercom.NewClient("access-token", "")

// After
import intercom "github.com/uffejaeger/intercom-go"

client, err := intercom.NewClient("access-token")
if err != nil {
return err
}
```

For applications that already keep credentials in the environment, use
`intercom.NewClientFromEnv()`. Select a non-US Intercom region at construction
time with `intercom.WithRegion(intercom.EU)` or `intercom.WithRegion(intercom.AU)`.

## Add a context and deadline

Legacy calls do not take a context. Give each replacement request a deadline
that covers its expected work, including any retry waits:

```go
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

contact, err := client.Contacts.Get(ctx, contactID)
```

## Map common contact workflows

| Legacy v2 workflow | `intercom-go` workflow |
| --- | --- |
| `ic.Contacts.FindByID(id)` | `client.Contacts.Get(ctx, id)` |
| `ic.Contacts.FindByUserID(id)` | `client.Contacts.GetByExternalID(ctx, id)` |
| `ic.Contacts.ListByEmail(email, ...)` | `client.Contacts.Search(ctx, intercom.ContactSearch{Field: "email", Operator: intercom.ContactSearchEquals, Value: email})` |
| `ic.Contacts.List(...)` | `client.Contacts.List(ctx)` for the first page, or `SearchIter` for cursor-paginated search results |
| `ic.Contacts.Create(...)` | `client.Contacts.Create(ctx, intercom.ContactCreate{...})` |
| `ic.Contacts.Update(...)` | `client.Contacts.Update(ctx, contactID, intercom.ContactUpdate{...})` |

The older SDK distinguishes users and contacts in places where the current
Intercom API uses contacts. Review each legacy user call against the current
API before moving it; do not assume a mechanical method rename is correct.

## Replace client options and HTTP tracing

Pass options when constructing the client instead of calling `ic.Option` later:

```go
client, err := intercom.NewClient("access-token",
intercom.WithHTTPClient(http.DefaultClient),
intercom.WithRetry(intercom.RetryConfig{MaxAttempts: 3}),
intercom.WithResponseHook(func(info intercom.ResponseInfo) {
log.Printf("status=%d request_id=%s remaining=%s",
info.StatusCode, info.RequestID, info.RateLimitRemaining)
}),
)
```

`WithResponseHook` is the replacement for ad-hoc transport tracing when the
goal is observability. Keep hooks fast and never log access tokens, request
bodies, or customer data.

## Paginate conversations safely

Use an iterator rather than manually retaining a cursor:

```go
iter := client.Conversations.ListIter(ctx, intercom.CursorPageOptions{PerPage: 25})
for iter.Next() {
conversation := iter.Conversation()
// Process the item without logging customer data.
_ = conversation
}
if err := iter.Err(); err != nil {
return err
}
```

The iterator stops with `intercom.ErrPaginationStalled` if Intercom repeats a
cursor, preventing an accidental infinite loop.

## Handle errors and webhooks

Use `intercom.IsNotFound(err)` for missing resources and `errors.As` with
`*intercom.ErrorResponse` when an application needs response status, headers,
or Intercom's request ID. For webhooks, use `ParseAndVerifyWebhook` so the
signature is checked against the exact request bytes before parsing.

See the runnable [webhook](../examples/verify_webhook),
[conversation](../examples/list_conversations), and
[observability](../examples/observe_rate_limits) examples. Consult the
[production guide](production.md) before enabling retries for an existing
write path.
33 changes: 33 additions & 0 deletions examples/list_conversations/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// list_conversations lists conversation pages lazily without retaining cursors.
package main

import (
"context"
"fmt"
"log"
"time"

intercom "github.com/uffejaeger/intercom-go"
)

func main() {
client, err := intercom.NewClientFromEnv()
if err != nil {
log.Fatal(err)
}

ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()

iter := client.Conversations.ListIter(ctx, intercom.CursorPageOptions{PerPage: 25})
count := 0
for iter.Next() {
// Process the conversation here. Avoid logging customer data by default.
count++
}
if err := iter.Err(); err != nil {
log.Fatal(err)
}

fmt.Printf("processed %d conversations\n", count)
}
32 changes: 32 additions & 0 deletions examples/observe_rate_limits/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// observe_rate_limits records request metadata without logging credentials or payloads.
package main

import (
"context"
"log"

intercom "github.com/uffejaeger/intercom-go"
)

func main() {
client, err := intercom.NewClientFromEnv(
intercom.WithResponseHook(func(info intercom.ResponseInfo) {
log.Printf("attempt=%d/%d status=%d request_id=%s remaining=%s reset=%s duration=%s",
info.Attempt,
info.MaxAttempts,
info.StatusCode,
info.RequestID,
info.RateLimitRemaining,
info.RateLimitReset,
info.Duration,
)
}),
)
if err != nil {
log.Fatal(err)
}

if _, err := client.Admins.Me(context.Background()); err != nil {
log.Fatal(err)
}
}
43 changes: 43 additions & 0 deletions examples/verify_webhook/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// verify_webhook runs a minimal HTTP endpoint that verifies Intercom webhooks
// before processing their payloads.
package main

import (
"errors"
"fmt"
"log"
"net/http"
"os"

intercom "github.com/uffejaeger/intercom-go"
)

func main() {
clientSecret := os.Getenv("INTERCOM_CLIENT_SECRET")
if clientSecret == "" {
log.Fatal("INTERCOM_CLIENT_SECRET is required")
}

http.HandleFunc("/webhooks/intercom", func(w http.ResponseWriter, r *http.Request) {
event, err := intercom.ParseAndVerifyWebhook(r, clientSecret, 0)
if err != nil {
switch {
case errors.Is(err, intercom.ErrWebhookPayloadTooLarge):
http.Error(w, "payload too large", http.StatusRequestEntityTooLarge)
case errors.Is(err, intercom.ErrWebhookSignatureMissing),
errors.Is(err, intercom.ErrWebhookSignatureInvalid),
errors.Is(err, intercom.ErrWebhookSignatureUnsupported):
http.Error(w, "invalid webhook signature", http.StatusUnauthorized)
default:
http.Error(w, "invalid webhook", http.StatusBadRequest)
}
return
}

// Decode event.Data.Item only for topics this endpoint handles.
log.Printf("verified webhook topic=%s id=%s", event.Topic, event.ID)
fmt.Fprintln(w, "ok")
})

log.Fatal(http.ListenAndServe(":8080", nil))
}