Help us shape the future of Go! Take the 2026 Go Developer Survey

msgraph

package
v1.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package msgraph is a minimal, hand-rolled Microsoft Graph client for Mail and Teams chat messaging - no official Go SDK is vendored in this module, same reasoning as core/connectors' sharepoint_graph.go: only a handful of call shapes are needed, not the full Graph surface a generated SDK would bring in.

This is shared, tenant-agnostic logic used two ways: seshat-backend wraps it in real inbox.Connector implementations (inbox/outlook/inbox/teams) for the local Inbox workflow, and seshat-server wraps it in a small self-hosted MCP server (internal/server/msgraphmcp) so the workspace chat agent can call it as a tool, bridged through a per-employee OAuth- connected account the same way every other agent_action connector kind works. Neither side stores or manages OAuth tokens here - callers pass an already-token-bound *http.Client, mirroring sharepoint_graph.go's spGraphClient.

Index

Constants

This section is empty.

Variables

View Source
var MailScopes = []string{
	"openid",
	"profile",
	"offline_access",

	"https://graph.microsoft.com/Mail.ReadWrite",
	"https://graph.microsoft.com/Mail.Send",

	"https://graph.microsoft.com/User.Read",
}

MailScopes are write-capable, unlike core/connectors' SharePointScopes (deliberately read-only, a Knowledge connector never writing back) - Outlook Mail is used for Inbox replies and agent-sent mail, so it needs Mail.Send too.

View Source
var TeamsScopes = []string{
	"openid",
	"profile",
	"offline_access",
	"https://graph.microsoft.com/Chat.Read",
	"https://graph.microsoft.com/ChatMessage.Send",
}

TeamsScopes cover 1:1/group chats only for v1 - Team channel messages need Channel.ReadBasic.All/ChannelMessage.Send, which require tenant-admin consent, a materially different consent story left for a follow-up if actually needed.

Functions

func SetBaseURLForTesting

func SetBaseURLForTesting(url string) (restore func())

SetBaseURLForTesting points this package's Microsoft Graph base URL at a test server, returning a function that restores the real value - for use by other packages' tests (e.g. seshat-server/internal/server/msgraphmcp) that need to verify a request/Bearer token reaches this package's HTTP calls without hitting the real Microsoft Graph host. Never call this outside a test.

Types

type Attachment

type Attachment struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	ContentType string `json:"contentType"`
	Size        int    `json:"size"`
}

Attachment is one attachment's metadata (no bytes - see Client.GetAttachment for the on-demand download).

type Category

type Category struct {
	ID          string `json:"id,omitempty"`
	DisplayName string `json:"displayName"`
	Color       string `json:"color,omitempty"`
}

Category is an Outlook "master category" - the closest Graph equivalent to a Gmail label, but structurally different: it's a flat, org-wide named color swatch (ListCategories/CreateCategory/DeleteCategory manage the swatch itself), and a message's Categories field is a plain string array of names Graph replaces wholesale on PATCH, not an add/remove of IDs the way Gmail's ModifyMessageRequest works (see ModifyMessageCategories).

type Chat

type Chat struct {
	ID    string `json:"id"`
	Topic string `json:"topic"`
}

Chat is one 1:1 or group chat the connected account is a member of.

type ChatMessage

type ChatMessage struct {
	ID              string           `json:"id"`
	ChatID          string           `json:"chatId"`
	From            *ChatMessageFrom `json:"from,omitempty"`
	Body            *MessageBody     `json:"body,omitempty"`
	CreatedDateTime string           `json:"createdDateTime"`
	DeletedDateTime string           `json:"deletedDateTime,omitempty"`
}

ChatMessage is one message within a Chat.

type ChatMessageFrom

type ChatMessageFrom struct {
	User *ChatMessageUser `json:"user,omitempty"`
}

ChatMessageFrom is the sender of a Teams chat message: the user, when the message was sent by one.

type ChatMessageUser

type ChatMessageUser struct {
	ID          string `json:"id"`
	DisplayName string `json:"displayName"`
}

ChatMessageUser is the user who sent a Teams chat message: an identifier and a display name.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a minimal hand-rolled Microsoft Graph HTTP client.

func New

func New(httpClient *http.Client) *Client

New builds a Client from an already-token-bound *http.Client (e.g. oauthConfig.Client(ctx, token)) - this package never sees a raw token or does its own OAuth.

func (*Client) Compose

func (c *Client) Compose(ctx context.Context, opts ComposeOptions) error

Compose sends a message with CC/BCC, unlike SendMail's minimal shape.

func (*Client) CreateCategory

func (c *Client) CreateCategory(ctx context.Context, name string) (Category, error)

CreateCategory creates a new master category.

func (*Client) CreateDraft

func (c *Client) CreateDraft(ctx context.Context, opts ComposeOptions) (*Message, error)

CreateDraft saves a message as a draft instead of sending it - a plain POST /me/messages creates a draft by default in Graph, unlike Gmail's dedicated Drafts.Create endpoint.

func (*Client) DeleteCategory

func (c *Client) DeleteCategory(ctx context.Context, id string) error

DeleteCategory deletes a master category.

func (*Client) DeleteDraft

func (c *Client) DeleteDraft(ctx context.Context, draftID string) error

DeleteDraft discards a draft without sending it.

func (*Client) GetAttachment

func (c *Client) GetAttachment(ctx context.Context, messageID, attachmentID string) ([]byte, error)

GetAttachment downloads one attachment's bytes. Unlike Gmail (a separate download-by-ID call returning base64url), Graph's file attachment resource carries its bytes as standard base64 inline on the same attachment object - fetched here via $select to avoid also pulling every other attachment's bytes that a plain ListAttachments call would.

func (*Client) GetMessage

func (c *Client) GetMessage(ctx context.Context, id string) (*Message, error)

GetMessage fetches one message by ID with Graph's full default property set (unlike ListMessagesDelta's $select-trimmed page, this includes the full Body).

func (*Client) GetProfile

func (c *Client) GetProfile(ctx context.Context) (Profile, error)

GetProfile returns the connected account's own identity.

func (*Client) LatestMessageInConversation

func (c *Client) LatestMessageInConversation(ctx context.Context, conversationID string) (*Message, error)

LatestMessageInConversation returns the most recent message in a mail conversation (Graph's thread-equivalent) - used to build a properly threaded reply, since ReplyToMessage needs a specific message ID, not a conversation ID (what NormalizedMessage.ExternalThreadID actually stores for mail).

func (*Client) ListAttachments

func (c *Client) ListAttachments(ctx context.Context, messageID string) ([]Attachment, error)

ListAttachments returns attachment metadata for a message.

func (*Client) ListCategories

func (c *Client) ListCategories(ctx context.Context) ([]Category, error)

ListCategories returns every master category defined for this mailbox.

func (*Client) ListChatMessages

func (c *Client) ListChatMessages(ctx context.Context, chatID, cursor string) ([]ChatMessage, string, error)

ListChatMessages returns chatID's messages, paginated via @odata.nextLink only - unlike Mail/SharePoint, Teams chat messages have no delta endpoint, so cursor here is just "the next page link to resume from," not a persistent incremental-sync cursor across calls separated in time (a resumed sync after a gap re-lists from the most recent messages).

func (*Client) ListChats

func (c *Client) ListChats(ctx context.Context) ([]Chat, error)

ListChats returns every chat the connected account is a member of.

func (*Client) ListDrafts

func (c *Client) ListDrafts(ctx context.Context, top int) ([]Message, error)

ListDrafts returns messages in the well-known Drafts folder.

func (*Client) ListMessagesDelta

func (c *Client) ListMessagesDelta(ctx context.Context, cursor string) ([]Message, string, error)

ListMessagesDelta returns every inbox message changed since cursor (empty cursor = full enumeration, a first-ever sync), paging through @odata.nextLink internally and returning once @odata.deltaLink is reached - the caller only ever sees one logical page plus the new deltaLink to persist as the next cursor. Mirrors core/connectors' sharepoint deltaPage shape.

func (*Client) ListMessagesInConversation

func (c *Client) ListMessagesInConversation(ctx context.Context, conversationID string) ([]Message, error)

ListMessagesInConversation returns every message in a conversation (Graph's thread-equivalent), oldest first - the multi-message counterpart to LatestMessageInConversation's single-message lookup.

func (*Client) ModifyMessageCategories

func (c *Client) ModifyMessageCategories(ctx context.Context, id string, addCategories, removeCategories []string) error

ModifyMessageCategories adds and/or removes category names on a message - Gmail-like add/remove ergonomics layered over Graph's actual full-replace PATCH (see Category's doc comment): reads the message's current Categories, computes the new set, then PATCHes the whole array. Not atomic against a concurrent modification (Graph offers no partial-update primitive here), acceptable for an agent-driven triage action.

func (*Client) MoveMessage

func (c *Client) MoveMessage(ctx context.Context, id, destinationFolderID string) (string, error)

MoveMessage moves a message to a well-known or custom mail folder, returning the moved message's ID (Graph mints a new one on move) - the primitive TrashMessage/UntrashMessage are built on.

func (*Client) ReplyToMessage

func (c *Client) ReplyToMessage(ctx context.Context, messageID, body string) error

ReplyToMessage replies to an existing message - Graph handles In-Reply-To/References threading natively, unlike Gmail's manual header construction.

func (*Client) SearchMessages

func (c *Client) SearchMessages(ctx context.Context, query string, maxResults int) ([]Message, error)

SearchMessages runs a free-text Graph $search query (matches subject, body and sender, closest Graph equivalent to Gmail's search operators - Graph has no from:/is:unread/has:attachment query syntax of its own, so this is plain full-text search, not a structured query language). maxResults <= 0 uses searchMessageLimit.

func (*Client) SendChatMessage

func (c *Client) SendChatMessage(ctx context.Context, chatID, body string) error

SendChatMessage posts a new message into an existing chat.

func (*Client) SendDraft

func (c *Client) SendDraft(ctx context.Context, draftID string) error

SendDraft sends a previously saved draft.

func (*Client) SendMail

func (c *Client) SendMail(ctx context.Context, to []string, subject, body string) error

SendMail sends a fresh (non-reply) message.

func (*Client) SetMessageRead

func (c *Client) SetMessageRead(ctx context.Context, id string, read bool) error

SetMessageRead sets a message's read/unread state.

func (*Client) TrashMessage

func (c *Client) TrashMessage(ctx context.Context, id string) (string, error)

TrashMessage moves a message to the well-known Deleted Items folder - Graph has no separate trash flag the way Gmail does, so this (like UntrashMessage) is a MoveMessage call, and the returned ID differs from the original (see MoveMessage's doc comment).

func (*Client) UntrashMessage

func (c *Client) UntrashMessage(ctx context.Context, id string) (string, error)

UntrashMessage moves a message from Deleted Items back to the Inbox.

type ComposeOptions

type ComposeOptions struct {
	To, Cc, Bcc []string
	Subject     string
	Body        string
}

ComposeOptions is the CC/BCC-capable header set Compose/CreateDraft accept - SendMail's minimal to/subject/body is kept as-is since existing callers (inbox/outlook's connector) only need that shape. Unlike Gmail's ComposeOptions, there is no ThreadID: Graph has no client-settable "send within this thread" field on message creation - conversationId is server-assigned from the Subject/In-Reply-To Graph infers, not something a caller can request.

type EmailAddress

type EmailAddress struct {
	Name    string `json:"name"`
	Address string `json:"address"`
}

EmailAddress is a display name and an address.

type GraphError

type GraphError struct {
	Status int
	Body   string
}

GraphError carries the HTTP status so callers can special-case specific responses (e.g. a 404 meaning "no such chat/message").

func (*GraphError) Error

func (e *GraphError) Error() string

Error returns the status and the body of the answer of Microsoft Graph.

type Message

type Message struct {
	ID               string           `json:"id"`
	ConversationID   string           `json:"conversationId"`
	Subject          string           `json:"subject"`
	BodyPreview      string           `json:"bodyPreview"`
	Body             *MessageBody     `json:"body,omitempty"`
	From             *Recipient       `json:"from,omitempty"`
	ToRecipients     []RecipientEntry `json:"toRecipients,omitempty"`
	CcRecipients     []RecipientEntry `json:"ccRecipients,omitempty"`
	ReceivedDateTime string           `json:"receivedDateTime"`
	// IsRead/Categories/HasAttachments mirror Gmail's Message.Unread/
	// LabelIds/Attachments - Graph's read state is a plain boolean (not a
	// pseudo-label like Gmail's UNREAD), and categories are a flat
	// caller-assigned string array, not IDs from a separate resource the
	// way Gmail label IDs are (see ListCategories's doc comment).
	IsRead         bool      `json:"isRead,omitempty"`
	Categories     []string  `json:"categories,omitempty"`
	HasAttachments bool      `json:"hasAttachments,omitempty"`
	Deleted        *struct{} `json:"@removed,omitempty"`
}

Message is one email as returned by a mail delta page.

type MessageBody

type MessageBody struct {
	ContentType string `json:"contentType"`
	Content     string `json:"content"`
}

MessageBody is the body of a mail message: its content type (for example Text or HTML) and its content.

type Profile

type Profile struct {
	DisplayName       string `json:"displayName"`
	Mail              string `json:"mail"`
	UserPrincipalName string `json:"userPrincipalName"`
}

Profile is the connected account's own mailbox identity.

type Recipient

type Recipient struct {
	EmailAddress EmailAddress `json:"emailAddress"`
}

Recipient is the sender of a message, in its from field.

type RecipientEntry

type RecipientEntry struct {
	EmailAddress EmailAddress `json:"emailAddress"`
}

RecipientEntry is one recipient of a message, in its toRecipients and ccRecipients lists.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL