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 ¶
- Variables
- func SetBaseURLForTesting(url string) (restore func())
- type Attachment
- type Category
- type Chat
- type ChatMessage
- type ChatMessageFrom
- type ChatMessageUser
- type Client
- func (c *Client) Compose(ctx context.Context, opts ComposeOptions) error
- func (c *Client) CreateCategory(ctx context.Context, name string) (Category, error)
- func (c *Client) CreateDraft(ctx context.Context, opts ComposeOptions) (*Message, error)
- func (c *Client) DeleteCategory(ctx context.Context, id string) error
- func (c *Client) DeleteDraft(ctx context.Context, draftID string) error
- func (c *Client) GetAttachment(ctx context.Context, messageID, attachmentID string) ([]byte, error)
- func (c *Client) GetMessage(ctx context.Context, id string) (*Message, error)
- func (c *Client) GetProfile(ctx context.Context) (Profile, error)
- func (c *Client) LatestMessageInConversation(ctx context.Context, conversationID string) (*Message, error)
- func (c *Client) ListAttachments(ctx context.Context, messageID string) ([]Attachment, error)
- func (c *Client) ListCategories(ctx context.Context) ([]Category, error)
- func (c *Client) ListChatMessages(ctx context.Context, chatID, cursor string) ([]ChatMessage, string, error)
- func (c *Client) ListChats(ctx context.Context) ([]Chat, error)
- func (c *Client) ListDrafts(ctx context.Context, top int) ([]Message, error)
- func (c *Client) ListMessagesDelta(ctx context.Context, cursor string) ([]Message, string, error)
- func (c *Client) ListMessagesInConversation(ctx context.Context, conversationID string) ([]Message, error)
- func (c *Client) ModifyMessageCategories(ctx context.Context, id string, addCategories, removeCategories []string) error
- func (c *Client) MoveMessage(ctx context.Context, id, destinationFolderID string) (string, error)
- func (c *Client) ReplyToMessage(ctx context.Context, messageID, body string) error
- func (c *Client) SearchMessages(ctx context.Context, query string, maxResults int) ([]Message, error)
- func (c *Client) SendChatMessage(ctx context.Context, chatID, body string) error
- func (c *Client) SendDraft(ctx context.Context, draftID string) error
- func (c *Client) SendMail(ctx context.Context, to []string, subject, body string) error
- func (c *Client) SetMessageRead(ctx context.Context, id string, read bool) error
- func (c *Client) TrashMessage(ctx context.Context, id string) (string, error)
- func (c *Client) UntrashMessage(ctx context.Context, id string) (string, error)
- type ComposeOptions
- type EmailAddress
- type GraphError
- type Message
- type MessageBody
- type Profile
- type Recipient
- type RecipientEntry
Constants ¶
This section is empty.
Variables ¶
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.
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 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 ¶
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 ¶
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 ¶
CreateCategory creates a new master category.
func (*Client) CreateDraft ¶
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 ¶
DeleteCategory deletes a master category.
func (*Client) DeleteDraft ¶
DeleteDraft discards a draft without sending it.
func (*Client) GetAttachment ¶
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 ¶
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 ¶
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 ¶
ListAttachments returns attachment metadata for a message.
func (*Client) ListCategories ¶
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) ListDrafts ¶
ListDrafts returns messages in the well-known Drafts folder.
func (*Client) ListMessagesDelta ¶
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 ¶
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 ¶
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 ¶
SendChatMessage posts a new message into an existing chat.
func (*Client) SetMessageRead ¶
SetMessageRead sets a message's read/unread state.
func (*Client) TrashMessage ¶
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).
type ComposeOptions ¶
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 ¶
EmailAddress is a display name and an address.
type GraphError ¶
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 ¶
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.