A focused, zero-dependency Go client for the Microsoft Graph mail API — read messages, manage
change-notification subscriptions, and run the OAuth grants that get you a token. You supply the
access token (via a TokenSource); the client does the rest.
Extracted and generalized from a production Microsoft 365 mail integration.
- Mail —
GetMe,FetchMessage,ListRecentMessages, andGetRawMIME(the RFC-822 bytes), with\Seenmapping and a size fallback. - Subscriptions —
CreateSubscription/RenewSubscription/DeleteSubscriptionfor Graph change-notification webhooks. - OAuth grants —
Refresh,RefreshWithScope, andExchangeCode(auth-code + PKCE, public client) for minting tokens. - Robustness —
Retry-Afterhandling (HonorRetryAfter), richAPIErrordiagnostics,ErrInvalidGrant/IsInvalidGrant, and the AADSTS/common→/consumerslearned-endpoint fallback (bounded, concurrency-safe cache). - Zero dependencies — standard library only.
import "github.com/biglill/msgraph-mail" // package msgraph
// Bring your own token. StaticToken wraps a fixed one; supply your own TokenSource
// to refresh transparently.
c := msgraph.New(msgraph.StaticToken(accessToken))
me, err := c.GetMe(ctx)
msgs, err := c.ListRecentMessages(ctx, 25)
raw, err := c.GetRawMIME(ctx, msgs[0].ID)Refresh tokens yourself and feed a live TokenSource:
c := msgraph.New(func(ctx context.Context) (string, error) {
tok, err := c.Refresh(ctx, clientID, refreshToken) // or cache and only refresh near expiry
if err != nil { return "", err }
return tok.AccessToken, nil
})Subscribe to change notifications:
sub, err := c.CreateSubscription(ctx, msgraph.SubscriptionCreate{
Resource: "me/mailFolders('Inbox')/messages",
ChangeType: "created",
NotificationURL: "https://your.app/webhooks/graph",
ClientState: clientState, // your secret, echoed back for verification
ExpirationDateTime: time.Now().Add(60 * time.Minute),
})TokenSource is the one seam:
type TokenSource func(ctx context.Context) (string, error)Client exposes HTTP, BaseURL, and TokenEndpoint (sensible Microsoft defaults via
DefaultBaseURL / DefaultTokenEndpoint), plus WithHTTP to inject your own *http.Client.
go test ./... # 27 tests against httptest servers
go test -race ./...MIT — see LICENSE.