Skip to content
Engineering

The go-notification docs documented a Laravel that doesn't exist

A full accuracy pass on the go-notification docs — replacing a fictional Laravel-style API with the real Go one — plus an On this page menu and the same LLM-friendly endpoints.

Andrian Prasetya Andrian Prasetya
go-notification docs, now AI-ready: multi-channel notification delivery for Go with Copy as Markdown, Open in LLM, and an On this page table of contents.

This is the fourth docs overhaul in a row — go-migration, open-swaggo, go-audit, and now go-notification. The first had drifted, the second had drifted into fiction, the third kept two copies of the truth. go-notification managed to be the worst of the lot: the docs described a notifications library that doesn’t exist, written in the shape of one that does — Laravel.

Documenting the framework you came from

go-notification is a Go package. But large parts of its docs read like a port of Laravel’s notification system — facades, fluent helpers, and method names lifted straight out of PHP. None of it compiled, because none of it was the API the package actually ships. A reader following the Getting Started guide couldn’t send a single notification, because the functions in the example weren’t real.

So I rewrote the whole site — roughly fifty pages — against the actual source at github.com/gopackx/go-notification.

The corrections that mattered most

Registering a channel

The old docs registered channels through a facade-style helper. The real API is a method on the manager that takes a channel value.

go cmd/example/main.go Before
// Laravel cosplay — none of this compiles
Notification::route('mail', addr)
  ->notify(new InvoicePaid());
go cmd/example/main.go After
// Register the channel on the manager,
// then build & send the notification.
mgr.RegisterChannel(ch)
mgr.Send(ctx, recipient, msg)

Notifiable is an interface, not a trait

There’s no trait to mix in. A recipient is anything implementing Notifiable — two methods, both returning the values the dispatcher actually reads.

go notifiable.go
// The real contract — two methods.
type Notifiable interface {
  GetID() string
  RouteNotificationFor(channel string) string
}

RouteNotificationFor returns a string — the address for the given channel — not some Route object the old docs invented.

Building a message with Set*

The old docs used a fluent ->line()->action() chain. The real builder uses plain Set* methods, and chat goes through a single unified ToChat.

go message.go Before
// Fluent helpers that never existed in Go
(new MailMessage)
  ->subject('Hi')
  ->line('Welcome')
  ->action('View', $url);
go message.go After
// Plain Set* methods; chat goes through ToChat.
msg.SetSubject("Hi").SetText("Welcome")

// Slack, Telegram, Discord, Teams: one path
msg.ToChat(slack, telegram, discord, teams)

The pieces that are real — and were documented wrong

The package has a genuinely capable middle layer that the old docs either fudged or skipped. These are now documented as they actually behave:

  • Config — the real loader and fields, not invented ones.
  • Retry — permanent failures are signalled with middleware.ErrPermanent, so the retry middleware knows when to stop instead of hammering a dead endpoint.
  • Rate limitingSetRateLimit on the channel, the actual knob.
  • Redactionmiddleware.Redact scrubs sensitive fields before they hit a log or a transport.
  • Database channel — in-app notifications via NewSQLStore plus a migrate step, both real and now shown end to end.

What the docs now actually cover

With the fiction gone, the site documents the seven channels the library really ships:

// channels 7 surfaces
01
Email
smtpsendgridsespostmarkmailgunresendloops
02
WhatsApp
twiliometa-cloud360dialogvonagewhatsapp-business
03
SMS
twiliovonageplivomessagebird
04
Chat
slacktelegramdiscordteams
05
Push
firebase cloud messaging fcm
06
Database
in-app notifications via NewSQLStore
07
Webhook
HTTP signed delivery

”On this page”

A smaller, long-overdue fix: the right-hand On this page table of contents — the Clerk-style in-page nav that lets you jump between sections of a long doc — was disabled. It’s on now. On the longer channel and reference pages, that’s the difference between scanning and scrolling.

Docs for humans and machines

The LLM-friendly layer is the same one go-migration, open-swaggo, and go-audit got, now on go-notification — which, given how wrong the old text was, matters more here than anywhere: a model scraping the old HTML would have confidently taught you a Laravel API that can’t be compiled.

  • /llms.txt — a compact index of every docs page, the convention agents look for first.
  • Raw Markdown per page — every page is reachable as clean source under /api/md/..., frontmatter stripped, so a model gets self-describing text instead of HTML.
  • A “Copy as Markdown” button — copies the page as clean Markdown, JSX components flattened, ready to paste into ChatGPT or Claude.
  • An “Open in LLM” dropdown — “View raw Markdown”, “Open in ChatGPT”, or “Open in Claude”, each opening the page in the model with its context.

Because the raw route serves the same Markdown the page renders from, the machine view and the human view can’t drift apart. If the example you read compiles, so does the one an assistant copies — they’re the same bytes.

Four down

The pattern across all four is one sentence by now: documentation is a promise, and the failure modes are just different ways of breaking it — drift, fiction, two copies, or porting the docs of the framework you used to use. go-notification was the fiction made worse by a wrong accent. The docs now match the package, have a working table of contents, and read cleanly to both people and models.

If you’re using go-notification and an example doesn’t compile, that’s now a bug — tell me on the repo.