msgraph

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 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