github.com/thinwrap/notifications-go
thinwrap/notifications-go for Go — unified facade for email, SMS, push, and chat across 35 providers. One constructor per channel; switch vendor by changing the config struct. Truly zero deps (net/http only), stateless, BYO *http.Client.
Go constraints
- Runtime dependencies
- Zero runtime dependencies
- BYO HTTP client
- injectable *http.Client via the WithHTTPClient option
- Provenance
- Go module checksum database (sum.golang.org)
- Minimum runtime
- Go 1.18+
- Truly zero dependencies — the standard library net/http is the only HTTP surface, and AWS SigV4 (Bedrock) / JWT signing (FCM, APNs) are hand-rolled on crypto/*.
- Distributed as a Go module with no build step or codegen: the git tag IS the release. The public module proxy serves it directly and integrity is verified through the Go checksum database (sum.golang.org) and your go.sum — there is no separately signed release artifact, and none is needed for `go get`.
- The default *http.Client never follows redirects — a 3xx surfaces as an error rather than silently re-sending auth headers to another host.
30-second install
go get github.com/thinwrap/notifications-go2-minute end-to-end example
One constructor per channel; the config struct selects the provider. A provider that doesn't serve a channel is unrepresentable at compile time (e.g. TwilioConfig does not satisfy EmailConfig).
import "github.com/thinwrap/notifications-go"
e := notifications.NewEmail(notifications.SendgridConfig{
APIKey: os.Getenv("SENDGRID_API_KEY"),
From: "hello@acme.com",
})
res, err := e.Send(ctx, notifications.EmailSendInput{
To: "user@example.com",
Subject: "Welcome",
Text: "Thanks for signing up!",
HTML: "<p>Thanks for signing up!</p>",
})
if err != nil {
var ce *notifications.ConnectorError
if errors.As(err, &ce) { log.Printf("%s: %s", ce.ProviderCode, ce.ProviderMessage) }
return
}
fmt.Printf("status=%s id=%s\n", res.Status, res.ProviderMessageID)Switch vendor by changing the config struct — swap toResendConfig, PostmarkConfig,SesConfig, and so on. The Send(ctx, in) call and the EmailSendResult stay identical. Same pattern forNewSms, NewPush, and NewChat.
Supported providers
| Provider | Channel | Auth | README |
|---|---|---|---|
sendgrid SendGrid | bearer | docs | |
ses AWS SES | aws-sig-v4 | docs | |
mailgun Mailgun | basic | docs | |
postmark Postmark | header | docs | |
resend Resend | bearer | docs | |
brevo Brevo (Sendinblue) | header | docs | |
mailersend MailerSend | bearer | docs | |
mailtrap Mailtrap | bearer | docs | |
sparkpost SparkPost | header | docs | |
scaleway Scaleway | header | docs | |
twilio Twilio | sms | basic | docs |
vonage Vonage | sms | query | docs |
plivo Plivo | sms | basic | docs |
sns AWS SNS | sms | aws-sig-v4 | docs |
messagebird MessageBird (Bird) | sms | header | docs |
infobip Infobip | sms | header | docs |
telnyx Telnyx | sms | bearer | docs |
sinch Sinch | sms | bearer | docs |
textmagic Textmagic | sms | header | docs |
d7networks D7 Networks | sms | bearer | docs |
fcm Firebase Cloud Messaging | push | rs256-jwt | docs |
apns Apple Push Notifications | push | es256-jwt | docs |
expo Expo Push | push | bearer | docs |
one-signal OneSignal | push | header | docs |
pusher-beams Pusher Beams | push | bearer | docs |
wonderpush WonderPush | push | bearer | docs |
slack Slack | chat | webhook-url | docs |
discord Discord | chat | webhook-url | docs |
ms-teams Microsoft Teams | chat | webhook-url | docs |
telegram Telegram | chat | webhook-url | docs |
whatsapp-business WhatsApp Business | chat | bearer | docs |
line LINE | chat | bearer | docs |
google-chat Google Chat | chat | webhook-url | docs |
rocket-chat Rocket.Chat | chat | webhook-url | docs |
mattermost Mattermost | chat | webhook-url | docs |
Migration paths
Zero-dependency signing
No vendor SDKs and no third-party dependencies — AWS SigV4 (SES/SNS) and JWT signing (FCM RS256, APNs ES256) are hand-rolled on crypto/*. The wrapper is stateless: FCM/APNs sign a fresh token on everySend unless you supply a TokenCacheHook on the config.
Passthrough escape hatch
Only fields ≥90% of a channel's providers support are first-class on the input. Provider-specific fields flow through Passthrough(Body deep-merged, Headers/Queryshallow-merged; consumer values win):
res, err := s.Send(ctx, notifications.SmsSendInput{
To: "+15552223333",
Body: "hi",
Passthrough: ¬ifications.Passthrough{
Body: map[string]any{"mediaUrl": []any{"https://cdn/img.png"}}, // Twilio MMS
},
})Bring your own *http.Client
Inject any client satisfyinginterface{ Do(*http.Request) (*http.Response, error) }via WithHTTPClient. The default client never follows redirects. There is deliberately no top-level RetryAfterSeconds— the raw Retry-After header rides in Cause and retry is consumer policy.
Cross-language siblings
The TypeScript (/packages/notifications-ts) and PHP (/packages/notifications-php) variants ship the same facade shape and provider set — cross-language parity is a design goal, not a hard release gate.