Documentation
¶
Index ¶
- type ApproveSendDraftEndpoint
- type ApproveSendDraftRequest
- type CancelScheduledEndpoint
- type CancelScheduledRequest
- type ListMessagesEndpoint
- type ListMessagesRequest
- type MessageAttachmentInput
- type MessageSvc
- type MessageSvcConfig
- type RejectDraftEndpoint
- type RejectDraftRequest
- type SendMessageEndpoint
- type SendMessageRequest
- type UpdateDraftEndpoint
- type UpdateDraftRequest
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ApproveSendDraftEndpoint ¶
type ApproveSendDraftEndpoint struct{}
Approves a reply draft and sends it to the customer.
The draft becomes the sent message rather than spawning a copy: it takes its place in the case timeline, and the customer sees it as coming from "Customer Service". A draft on the email channel goes out as a reply on the case's email thread; otherwise it appears in the customer's conversation. Sending also moves the case to waiting on the customer.
Only the first approval of a draft sends it — approving one that is no longer open fails, so a concurrent double-approve cannot reach the customer twice. Customer accounts cannot approve drafts.
func (*ApproveSendDraftEndpoint) Materialize ¶
func (e *ApproveSendDraftEndpoint) Materialize() *apiendpoint.APIEndpoint[*ApproveSendDraftRequest, *apiresource.Message]
type ApproveSendDraftRequest ¶
type ApproveSendDraftRequest struct {
// The id of the draft to approve.
MessageID string `path:"id" validate:"required"`
// A unique client-generated key for this approval, such as a UUID.
ClientMessageID string `json:"client_message_id" validate:"required"`
}
Request to approve a customer-reply draft and send it to the customer.
func (*ApproveSendDraftRequest) SchemaExample ¶
func (*ApproveSendDraftRequest) SchemaExample() any
type CancelScheduledEndpoint ¶
type CancelScheduledEndpoint struct{}
Cancels a message that was scheduled for a future send, so it is never delivered.
You can only cancel a message you scheduled yourself, and only while it is still waiting to go out — once it has been delivered or has otherwise left the scheduled state, the request fails. The canceled message is kept as a record and never appears in the conversation.
func (*CancelScheduledEndpoint) Materialize ¶
func (e *CancelScheduledEndpoint) Materialize() *apiendpoint.APIEndpoint[*CancelScheduledRequest, *apiresource.Message]
type CancelScheduledRequest ¶
type CancelScheduledRequest struct {
// The id of the scheduled message to cancel.
MessageID string `path:"id" validate:"required"`
}
Request to cancel a pending scheduled message.
type ListMessagesEndpoint ¶
type ListMessagesEndpoint struct{}
Returns the messages in a conversation, newest first.
You must be an active participant. A customer reading their own case receives only the messages meant for them — internal team notes are never included.
func (*ListMessagesEndpoint) Materialize ¶
func (e *ListMessagesEndpoint) Materialize() *apiendpoint.APIEndpoint[*ListMessagesRequest, *apiresource.List[apiresource.Message]]
type ListMessagesRequest ¶
type ListMessagesRequest struct {
apiresource.PaginationRequest
// Conversation ID.
ConversationID string `path:"id" validate:"required"`
// Which set of the conversation's messages to return.
//
// Left unset, you get the delivered timeline. Pass `draft` for the case's reply drafts awaiting approval, or `scheduled` for the messages you yourself have queued for a future send, soonest first. Those two ignore paging and come back in a single response.
Status *constants.MessageStatus `query:"status"`
// Return only messages that come after this position in the timeline.
//
// Use it to catch up after a dropped realtime connection: pass the sequence of the last message you already have to fetch everything since.
AfterSequence *int64 `query:"after_sequence"`
}
Request to list a conversation's messages.
type MessageAttachmentInput ¶
type MessageAttachmentInput struct {
// What is being attached.
//
// - `file`: a document you uploaded to object storage first.
// - `image`: an uploaded image, rendered inline in the conversation.
// - `link`: an external web address, with nothing stored on our side.
// - `resource`: a reference to an in-app record, such as an order.
Kind constants.MessageAttachmentKind `json:"kind" validate:"required"`
// The key you uploaded the file to, taken from the upload-url response (file and image).
//
// The key must be one minted for this conversation and the file must already be uploaded, otherwise the send is rejected.
S3Key field.Optional[string] `json:"s3_key,omitzero"`
// The filename to display for the attachment (file and image).
Filename field.Optional[string] `json:"filename,omitzero"`
// The MIME content type of the uploaded file (file and image).
ContentType field.Optional[string] `json:"content_type,omitzero"`
// The size of the uploaded file in bytes (file and image).
SizeBytes field.Optional[int64] `json:"size_bytes,omitzero"`
// The web address being shared (link).
URL field.Optional[string] `json:"url,omitzero"`
// The type of the record being referenced, paired with `resource_id` (resource).
ResourceType field.Optional[string] `json:"resource_type,omitzero"`
// The id of the record being referenced, paired with `resource_type` (resource).
ResourceID field.Optional[string] `json:"resource_id,omitzero"`
}
A single attachment supplied when sending a message.
For an uploaded file or image, supply the `s3_key` you uploaded to; for a link, supply `url`; for a resource reference, supply `resource_type` and `resource_id`.
type MessageSvc ¶
type MessageSvc interface {
ListMessages(ctx context.Context, req *ListMessagesRequest) (*apiresource.List[apiresource.Message], *apierror.APIError)
SendMessage(ctx context.Context, req *SendMessageRequest) (*apiresource.Message, *apierror.APIError)
UpdateDraft(ctx context.Context, req *UpdateDraftRequest) (*apiresource.Message, *apierror.APIError)
ApproveSendDraft(ctx context.Context, req *ApproveSendDraftRequest) (*apiresource.Message, *apierror.APIError)
RejectDraft(ctx context.Context, req *RejectDraftRequest) (*apiresource.Message, *apierror.APIError)
CancelScheduled(ctx context.Context, req *CancelScheduledRequest) (*apiresource.Message, *apierror.APIError)
}
MessageSvc backs the chat message endpoints via the notification-service ChatService gRPC client. A message is the single resource for sent, scheduled, and draft content; the lifecycle endpoints (send, schedule, draft create/update/approve/reject/cancel) all return a Message.
func NewMessageSvc ¶
func NewMessageSvc(config *MessageSvcConfig) MessageSvc
type MessageSvcConfig ¶
type MessageSvcConfig struct {
// ChatClient (required) is the notification-service ChatService gRPC client.
ChatClient pb.ChatServiceClient
}
type RejectDraftEndpoint ¶
type RejectDraftEndpoint struct{}
Discards a reply draft without sending it to the customer.
The draft is kept as a rejected record for history and can no longer be edited or approved. Because the customer is still owed an answer, the case moves back to waiting on your team.
func (*RejectDraftEndpoint) Materialize ¶
func (e *RejectDraftEndpoint) Materialize() *apiendpoint.APIEndpoint[*RejectDraftRequest, *apiresource.Message]
type RejectDraftRequest ¶
type RejectDraftRequest struct {
// The id of the draft to reject.
MessageID string `path:"id" validate:"required"`
}
Request to discard an open customer-reply draft without sending it.
type SendMessageEndpoint ¶
type SendMessageEndpoint struct{}
Posts a message to a conversation.
With `mode` = `send` the message is delivered — immediately, or queued when `scheduled_at` is set — and a retry of an immediate send with the same `client_message_id` returns the original message rather than posting it twice. With `mode` = `draft` the message is proposed as a reply to the customer and held for a teammate to approve instead of being sent, and `channel` is required.
Sending requires you to be an active participant allowed to post: view-only participants cannot post, and in a direct message neither side of a block can. On a customer-facing case, replying to the customer moves the case to waiting on the customer, and proposing a draft moves it to awaiting approval.
func (*SendMessageEndpoint) Materialize ¶
func (e *SendMessageEndpoint) Materialize() *apiendpoint.APIEndpoint[*SendMessageRequest, *apiresource.Message]
type SendMessageRequest ¶
type SendMessageRequest struct {
// Conversation ID.
ConversationID string `path:"id" validate:"required"`
// Message body.
//
// Required unless the message carries at least one attachment or a resource link.
Body string `json:"body" validate:"required_without_all=Attachments LinkResourceID"`
// Whether to deliver the message now or hold it as a customer-reply draft.
//
// - `send`: delivers the message, immediately or at `scheduled_at`.
// - `draft`: proposes a reply to the customer on a customer-facing case and holds it for a teammate to approve before it goes out. Requires `channel`.
//
// A draft is built from `body`, `subject`, `channel`, and `source_thread_message_id` only — attachments, mentions, copied recipients, resource links, replies, and scheduling are not carried onto it.
Mode field.Optional[constants.MessageSendMode] `json:"mode,omitzero" default:"send"`
// The channel a draft will be sent over once it is approved (`mode` = `draft`).
//
// - `message`: appears in the customer's conversation timeline.
// - `email`: goes out as an email from the inbox the case is bridged to. Falls back to the conversation timeline if the case has no bridged inbox.
Channel field.Optional[constants.MessageChannel] `json:"channel,omitzero"`
// The internal thread message a draft is composed from, when drafting from a thread (`mode` = `draft`).
SourceThreadMessageID field.Optional[string] `json:"source_thread_message_id,omitzero"`
// Client-supplied dedupe key.
//
// Repeating an immediate send with the same value returns the message created by the first request instead of posting a second one, so a retry after a network failure is safe. Required when sending (`mode` = `send`); ignored for drafts.
ClientMessageID string `json:"client_message_id,omitzero"`
// Who the message is addressed to on a customer-facing case.
//
// - `customer`: a reply the customer sees, shown to them as coming from "Customer Service" and delivered as email when the case is bridged to an inbox.
// - `internal`: a team-only note the customer never sees.
//
// Messages are team-only unless you ask for `customer`, so an internal note can never leak by omission. Asking for `customer` on a conversation that has no customer is rejected.
//
// On a case bridged to an email inbox, a customer reply goes out as mail carrying only the body, subject, and copied recipients — attachments, mentions, resource links, and replies are dropped.
Audience field.Optional[constants.ConversationAudience] `json:"audience,omitzero" default:"internal"`
// The subject line for a customer reply sent by email.
//
// When omitted, the reply goes out as "Re:" the case title.
Subject field.Optional[string] `json:"subject,omitzero"`
// Additional email addresses to copy on a customer reply sent by email.
Cc []string `json:"cc,omitzero"`
// When set, hold the message and deliver it at this future time instead of sending it now.
//
// Only the body is carried into a scheduled send — attachments, mentions, copied recipients, resource links, replies, and audience are dropped, and it is delivered as an ordinary team-visible message. If you are no longer an active participant when it comes due, it is canceled instead of sent.
ScheduledAt field.Optional[time.Time] `json:"scheduled_at,omitzero"`
// The message this one is a reply to.
ReplyToMessageID field.Optional[string] `json:"reply_to_message_id,omitzero"`
// Type of a resource to link in the message, paired with `link_resource_id`.
//
// Linking a record lets clients render the message as a reference to it. A link counts in place of text, so a message may consist of nothing but the link.
LinkResourceType field.Optional[constants.ObjectType] `json:"link_resource_type,omitzero"`
// ID of a resource to link in the message, paired with `link_resource_type`.
LinkResourceID field.Optional[string] `json:"link_resource_id,omitzero"`
// Attachments to include with the message.
Attachments []MessageAttachmentInput `json:"attachments,omitzero"`
// Account user ids explicitly @mentioned in the message.
//
// A mention notifies the person even when they have muted the conversation.
Mentions []string `json:"mentions,omitzero"`
}
Request to post a message to a conversation.
func (*SendMessageRequest) SchemaExample ¶
func (*SendMessageRequest) SchemaExample() any
type UpdateDraftEndpoint ¶
type UpdateDraftEndpoint struct{}
Revises a reply draft before it is sent to the customer.
Only a draft that is still awaiting approval can be edited; once it has been approved, rejected, or superseded the request fails. Nothing reaches the customer until the draft is approved.
func (*UpdateDraftEndpoint) Materialize ¶
func (e *UpdateDraftEndpoint) Materialize() *apiendpoint.APIEndpoint[*UpdateDraftRequest, *apiresource.Message]
type UpdateDraftRequest ¶
type UpdateDraftRequest struct {
// The id of the draft to edit.
MessageID string `path:"id" validate:"required"`
// The revised reply body, replacing what the draft said before.
Body string `json:"body" validate:"required"`
// The revised subject line for a draft that will be sent by email.
//
// Leaving it out keeps the draft's current subject.
Subject field.Optional[string] `json:"subject,omitzero"`
}
Request to edit a still-open customer-reply draft message.
func (*UpdateDraftRequest) SchemaExample ¶
func (*UpdateDraftRequest) SchemaExample() any