Skip to content

angee.messaging.schema

GraphQL schema for the messaging addon — threads, messages, and the inbox.

Messages arrive through channel sync and the manager-owned ingest path; the console browses and moderates them through Hasura resources. Parts, participants, edges, and reactions remain nested read projections reached through their message/thread owners.

ChannelType

python
@strawberry_django.type(Channel)
class ChannelType(IntegrationLabelMixin, BridgeSyncStatusMixin, AngeeNode)

GraphQL projection of a connected message channel (e.g. an email account).

pairing_state

python
@strawberry_django.field
def pairing_state() -> PairingState | None

Return the live bridge's actual pairing state, or null for poll channels.

MessagingPairingQuery

python
@strawberry.type
class MessagingPairingQuery()

Admin pairing state for live message channels.

channel_pairing

python
@strawberry.field(permission_classes=ADMIN_PERMISSION_CLASSES)
def channel_pairing(id: PublicID) -> PairingProjection

Return pairing state in the caller's channel scope.

MessagingPairingMutation

python
@strawberry.type
class MessagingPairingMutation()

Admin lifecycle verbs for live message-channel pairing.

resume_channel_pairing

python
@strawberry.mutation(permission_classes=ADMIN_PERMISSION_CLASSES)
def resume_channel_pairing(id: PublicID) -> ActionResult

Resume retained pairing material or start a new pairing session.

submit_channel_password

python
@strawberry.mutation(permission_classes=ADMIN_PERMISSION_CLASSES)
@sensitive_variables("password", "material")
def submit_channel_password(id: PublicID, password: str) -> ActionResult

Submit one consume-once account password to the live channel session.

skip_channel_password

python
@strawberry.mutation(permission_classes=ADMIN_PERMISSION_CLASSES)
def skip_channel_password(id: PublicID) -> ActionResult

Skip one optional consume-once secret round.

reset_channel_pairing

python
@strawberry.mutation(permission_classes=ADMIN_PERMISSION_CLASSES)
def reset_channel_pairing(id: PublicID) -> ActionResult

Wipe released pairing material and restart with a fresh session.

disconnect_channel

python
@strawberry.mutation(permission_classes=ADMIN_PERMISSION_CLASSES)
def disconnect_channel(id: PublicID) -> ActionResult

Stop the live session while retaining reusable pairing material.

MessagingChannelMutation

python
@strawberry.type
class MessagingChannelMutation()

Owner/admin console mutation for deleting a connected channel.

delete_channel

python
@strawberry.mutation(name="delete_channel")
def delete_channel(id: PublicID, confirm: bool = False) -> DeletePreview

Preview, then optionally purge, one channel and everything it ingested.

Deleting a channel is a purge: its threads and messages FK the shared integrate.Integration parent with SET_NULL, so removing the channel row alone would orphan them. The preview forecasts the channel + its Integration row

  • the channel's thread/message totals and their CASCADE children as deleted — the single inventory :meth:ChannelManager.inventory counts with .count(), never materialized, so it stays fast on a 90k-message channel — and confirm runs that same scope through :meth:ChannelManager.purge (:meth:ThreadManager.teardown_for_channel first, then the channel + Integration row). Gated by the channel's own delete permission (its integration owner or a platform admin): a caller who cannot write the channel never resolves it, and :meth:ChannelManager.purge re-checks delete before any destructive work. A PROTECT/RESTRICT blocker on the confirm delete is surfaced as a blocked preview rather than a raw error.

FragmentType

python
@strawberry_django.type(Fragment)
class FragmentType(AngeeNode)

GraphQL projection of a content-addressed text fragment.

part_count

python
@strawberry.field(name="part_count")
def part_count() -> int

How many parts (across all messages) share this fragment — the dedup fact.

message_count

python
@strawberry.field(name="message_count")
def message_count() -> int

How many distinct messages reference this fragment through their parts.

PartType

python
@strawberry_django.type(Part)
class PartType(AngeeNode)

GraphQL projection of one message body part.

MessageSubtypeType

python
@strawberry_django.type(MessageSubtype)
class MessageSubtypeType(AngeeNode)

GraphQL projection of an Odoo-style message subtype.

TrackingValueType

python
@strawberry_django.type(TrackingValue)
class TrackingValueType(AngeeNode)

GraphQL projection of one tracked field change on a chatter message.

ParticipantType

python
@strawberry_django.type(Participant)
class ParticipantType(AngeeNode)

GraphQL projection of a thread/message participant.

MessageReactionGroupType

python
@strawberry.type
class MessageReactionGroupType()

Odoo-style grouped reactions for one message.

RecordHandleType

python
@strawberry_django.type(Handle)
class RecordHandleType(AngeeNode)

Envelope identity exposed by record chatter, without party backedges.

RecordMessageReactionGroupType

python
@strawberry.type
class RecordMessageReactionGroupType()

A record-message reaction summary with narrowed handle projections.

MessageType

python
@strawberry_django.type(Message)
class MessageType(AngeeNode)

GraphQL projection of a message.

parts

python
@strawberry.field
def parts() -> list[PartType]

Return this message's MIME parts in depth-first reading order.

title

python
@strawberry.field
def title() -> str

Return the message's title text — its TITLE part's fragment, or "".

No optimizer prefetch hint here: a query that also selects parts makes the optimizer build its own Prefetch("parts", …), and Django rejects a second queryset for the same lookup. The model method rides an existing parts prefetch when one is loaded and otherwise costs one indexed probe.

edges

python
@strawberry.field
def edges() -> list["MessageEdgeType"]

Return this message's cross-message edges, both directions.

The fragment-derived quote graph plus post relations — the "how it interconnects" read the content data view renders. Actor-scoped end to end: the owner (MessageEdgeManager.for_message) keeps only edges whose far endpoint the actor may read, because quote edges cross channels by construction.

reaction_groups

python
@strawberry.field
def reaction_groups(info: strawberry.Info) -> list[MessageReactionGroupType]

Return reactions grouped by content, with current-user state.

starred

python
@strawberry.field
def starred(info: strawberry.Info) -> bool

Return whether the current user has starred this message.

needaction

python
@strawberry.field
def needaction(info: strawberry.Info) -> bool

Return whether this message sits past the current user's read receipt.

can_edit

python
@strawberry_django.field(prefetch_related=["tracking_values"])
def can_edit(info: strawberry.Info) -> bool

Return whether the current actor may edit this message's body.

Delegates to the message's own :meth:Message.can_edit owner (post access plus the mail edit rule), passing the record post access resolved and memoized once per thread by :func:_message_post_access. The tracking_values prefetch hint lets the optimizer batch the edit-rule predicate instead of an exists() per row.

can_delete

python
@strawberry.field
def can_delete(info: strawberry.Info) -> bool

Return whether the current actor may delete this message.

Delegates to :meth:Message.can_delete (the record thread's post access; deletion carries no mail-kind restriction of its own), passing the post access memoized once per thread by :func:_message_post_access.

RecordMessageParentType

python
@strawberry_django.type(Message)
class RecordMessageParentType(AngeeNode)

Minimal parent-message context shown in record chatter.

RecordMessageType

python
@strawberry_django.type(Message)
class RecordMessageType(AngeeNode)

Record-gated message projection without generic inbox relation backedges.

parts

python
@strawberry.field
def parts() -> list[PartType]

Return this record message's MIME parts in depth-first reading order.

title

python
@strawberry.field
def title() -> str

Return the message title text from its sparse title part.

reaction_groups

python
@strawberry.field
def reaction_groups(
        info: strawberry.Info) -> list[RecordMessageReactionGroupType]

Return reaction groups with record-safe handle projections.

starred

python
@strawberry.field
def starred(info: strawberry.Info) -> bool

Return whether the current user has starred this message.

needaction

python
@strawberry.field
def needaction(info: strawberry.Info) -> bool

Return whether this message sits past the current user's read receipt.

can_edit

python
@strawberry_django.field(prefetch_related=["tracking_values"])
def can_edit(info: strawberry.Info) -> bool

Return the model-owned edit capability for this record message.

can_delete

python
@strawberry.field
def can_delete(info: strawberry.Info) -> bool

Return the model-owned delete capability for this record message.

ThreadType

python
@strawberry_django.type(Thread)
class ThreadType(AngeeNode)

GraphQL projection of a thread.

RecordThreadType

python
@strawberry_django.type(Thread)
class RecordThreadType(AngeeNode)

Record-gated thread summary without generic inbox backedges.

RecordThreadFollowerType

python
@strawberry_django.type(ThreadFollower)
class RecordThreadFollowerType(AngeeNode)

Record-scoped follower projection without message or thread backedges.

RecordThreadNotificationType

python
@strawberry_django.type(ThreadNotification)
class RecordThreadNotificationType(AngeeNode)

Record-scoped notification projection with narrowed message context.

RecordThreadActivityType

python
@strawberry_django.type(ThreadActivity)
class RecordThreadActivityType(AngeeNode)

Record-scoped activity projection without thread or attachment backedges.

state

python
@strawberry.field
def state() -> str

Return the Odoo-style activity state.

ThreadAttachmentType

python
@strawberry_django.type(ThreadAttachment)
class ThreadAttachmentType(AngeeNode)

GraphQL projection of a model-record thread attachment.

model_label

python
@strawberry.field(name="model_label")
def model_label() -> str

Return the attached target's model label (app_label.ModelName).

record_id

python
@strawberry.field(name="record_id")
def record_id() -> strawberry.ID

Return the attached target's stable public id (its sqid).

A parent pointer back to the record — the ID scalar every record reference uses (RecordReferenceInput.record_id); navigating it re-gates through the target's own record read (an assignee not granted the record may 404).

ThreadFollowerType

python
@strawberry_django.type(ThreadFollower)
class ThreadFollowerType(AngeeNode)

GraphQL projection of a user's thread membership — policy plus read receipt.

ThreadActivityType

python
@strawberry_django.type(ThreadActivity)
class ThreadActivityType(AngeeNode)

GraphQL projection of a scheduled record chatter activity.

state

python
@strawberry.field
def state() -> str

Return the Odoo-style activity state.

RecordPointerType

python
@strawberry.type
class RecordPointerType()

Minimal, read-only pointer back to the record a chatter row is attached to.

The record's human label, its app_label.ModelName, and its stable public id — the three facts a cross-record agenda needs to link back, each computed from the owning ThreadAttachment row alone (no target load, no parent-record read). Navigating the link re-gates through the record's own read (an assignee not granted the record may 404).

AgendaActivityType

python
@strawberry_django.type(ThreadActivity)
class AgendaActivityType(AngeeNode)

The actor's own scheduled activity, narrowed for the cross-record agenda.

activity_agenda delivers ThreadActivity rows to a bare assignee through the messaging/thread_activity.read user (assignee) arm — an assignment is itself the grant. Because that assignee holds no read on the parent record, thread, or attachment, this projection exposes only the activity's own fields, the assignee's own identity (user — always the actor, by the agenda filter), and the minimal record pointer (attachment → :class:RecordPointerType); it deliberately drops the parent thread and the attachment's metadata, which a to-one traversal would resolve unguarded and so leak to a bare assignee through the agenda. The record-scoped record_thread surface projects the full :class:ThreadActivityType for a user who can already read the parent record.

state

python
@strawberry.field
def state() -> str

Return the derived activity state (planned/today/overdue/done/canceled).

attachment

python
@strawberry.field
def attachment() -> RecordPointerType

Return the minimal pointer to the record this activity is attached to.

This is the explicit exception to :func:angee.graphql.relations.actor_scoped_to_one: a narrow-projection contract, not visibility redaction.

The attachment FK is primed elevated and by id on the agenda queryset (:meth:ThreadActivityQuerySet.with_record_pointers), so this reads no extra row; the three pointer facts come from the ThreadAttachment owner without loading or re-gating the target record.

ThreadNotificationType

python
@strawberry_django.type(ThreadNotification)
class ThreadNotificationType(AngeeNode)

GraphQL projection of a per-recipient delivery row (read state lives on receipts).

MessageEdgeType

python
@strawberry_django.type(MessageEdge)
class MessageEdgeType(AngeeNode)

GraphQL projection of a cross-message edge (quote/mention/crosspost/forward).

ReactionType

python
@strawberry_django.type(Reaction)
class ReactionType(AngeeNode)

GraphQL projection of an attributed reaction.

MessageStarType

python
@strawberry_django.type(MessageStar)
class MessageStarType(AngeeNode)

GraphQL projection of a user's starred message marker.

MessageSubtypeOptionType

python
@strawberry.type
class MessageSubtypeOptionType()

Follower-selectable chatter subtype option.

SuggestedRecipientType

python
@strawberry.type
class SuggestedRecipientType()

One Odoo-style suggested recipient for a record chatter composer.

RecordReferenceInput

python
@strawberry.input
class RecordReferenceInput()

Record identity for a model-backed chatter thread.

RecordThreadInput

python
@strawberry.input
class RecordThreadInput(RecordReferenceInput)

Fields accepted when fetching a model-backed chatter thread.

RecordMessagePostInput

python
@strawberry.input
class RecordMessagePostInput(RecordReferenceInput)

Fields accepted when posting an internal chatter message.

RecordMessageUpdateInput

python
@strawberry.input
class RecordMessageUpdateInput(RecordReferenceInput)

Fields accepted when editing an existing chatter comment.

RecordMessageDeleteInput

python
@strawberry.input
class RecordMessageDeleteInput(RecordReferenceInput)

Fields accepted when deleting an existing chatter message.

RecordMessageReactionInput

python
@strawberry.input
class RecordMessageReactionInput(RecordReferenceInput)

Fields accepted when reacting to a chatter message.

RecordMessageStarInput

python
@strawberry.input
class RecordMessageStarInput(RecordReferenceInput)

Fields accepted when starring or unstarring a chatter message.

RecordMessageDoneInput

python
@strawberry.input
class RecordMessageDoneInput(RecordReferenceInput)

Fields accepted when marking a chatter message done for the user.

RecordFollowInput

python
@strawberry.input
class RecordFollowInput(RecordReferenceInput)

Fields accepted when following or unfollowing a record chatter thread.

RecordActivityScheduleInput

python
@strawberry.input
class RecordActivityScheduleInput(RecordReferenceInput)

Fields accepted when scheduling a record activity.

RecordActivityFeedbackInput

python
@strawberry.input
class RecordActivityFeedbackInput()

Fields accepted when completing a record activity.

RecordActivityCancelInput

python
@strawberry.input
class RecordActivityCancelInput()

Fields accepted when canceling a record activity.

RecordThreadPayload

python
@strawberry.type
class RecordThreadPayload()

A record chatter thread, or the error that prevented resolving it.

RecordMessagePostPayload

python
@strawberry.type
class RecordMessagePostPayload()

A posted chatter message, or the error that prevented posting it.

RecordMessageUpdatePayload

python
@strawberry.type
class RecordMessageUpdatePayload()

An updated chatter message, or the error that prevented editing it.

RecordMessageDeletePayload

python
@strawberry.type
class RecordMessageDeletePayload()

A deleted chatter message id plus refreshed thread state, or an error.

RecordMessageReactionPayload

python
@strawberry.type
class RecordMessageReactionPayload()

A reacted chatter message, or the error that prevented reacting.

RecordMessageStarPayload

python
@strawberry.type
class RecordMessageStarPayload()

A starred/unstarred chatter message result, or the error that prevented it.

RecordMessageDonePayload

python
@strawberry.type
class RecordMessageDonePayload()

A message marked done for the user, or the error that prevented it.

RecordFollowPayload

python
@strawberry.type
class RecordFollowPayload()

A record follower update result, or the error that prevented it.

RecordActivityPayload

python
@strawberry.type
class RecordActivityPayload()

A record activity update result, or the error that prevented it.

MessagingQuery

python
@strawberry.type
class MessagingQuery()

Record- and actor-scoped chatter queries.

record_thread

python
@strawberry.field(name="record_thread")
def record_thread(info: strawberry.Info,
                  input: RecordThreadInput) -> RecordThreadPayload

Return the existing chatter thread attached to a model record.

record_thread_unread_count

python
@strawberry.field(name="record_thread_unread_count")
def record_thread_unread_count(
    info: strawberry.Info,
    model_label: Annotated[str, strawberry.argument(name="model_label")],
    record_id: Annotated[strawberry.ID,
                         strawberry.argument(name="record_id")]
) -> int

Return the request actor's unread count for a readable record thread.

activity_agenda

python
@strawberry.field(name="activity_agenda")
def activity_agenda(info: strawberry.Info,
                    window_start: date,
                    window_end: date,
                    include_done: bool = False) -> list[AgendaActivityType]

Return the current actor's assigned activities due within the window.

The actor's own agenda across records: ThreadActivity rows assigned to the actor (user), due within [window_start, window_end), ordered by due date. The window is the whole bound — no pagination. Done and canceled are excluded unless include_done. Overdue is neither stored nor filtered; it rides each row's state derivation.

Authorization rides the existing messaging/thread_activity.read user (assignee) arm — an assignment is itself the grant — so the read needs no parent-record re-gate and no record-read fan-out. Each row is projected as :class:AgendaActivityType: the activity's own fields, the actor's own identity, and the minimal record pointer — never the parent thread or attachment metadata, which a to-one traversal would otherwise resolve unguarded. The assignee FK is the request actor by construction, and the pointer attachment is primed elevated and by id on the queryset, so the projection needs no REBAC-guarded select_related (the pitfall) and no per-row pointer fan-out.

MessagingMutation

python
@strawberry.type
class MessagingMutation()

Record-backed chatter mutations.

post_record_message

python
@strawberry.mutation(name="post_record_message")
def post_record_message(
        info: strawberry.Info,
        input: RecordMessagePostInput) -> RecordMessagePostPayload

Post an internal comment to the record's chatter thread.

update_record_message

python
@strawberry.mutation(name="update_record_message")
def update_record_message(
        info: strawberry.Info,
        input: RecordMessageUpdateInput) -> RecordMessageUpdatePayload

Update an existing internal comment in the record's chatter thread.

delete_record_message

python
@strawberry.mutation(name="delete_record_message")
def delete_record_message(
        info: strawberry.Info,
        input: RecordMessageDeleteInput) -> RecordMessageDeletePayload

Delete a message from the record's chatter thread.

set_record_message_reaction

python
@strawberry.mutation(name="set_record_message_reaction")
def set_record_message_reaction(
        info: strawberry.Info,
        input: RecordMessageReactionInput) -> RecordMessageReactionPayload

Add, remove, or toggle the current user's reaction on a chatter message.

set_record_message_starred

python
@strawberry.mutation(name="set_record_message_starred")
def set_record_message_starred(
        info: strawberry.Info,
        input: RecordMessageStarInput) -> RecordMessageStarPayload

Set or toggle the current user's star on a chatter message.

mark_record_message_done

python
@strawberry.mutation(name="mark_record_message_done")
def mark_record_message_done(
        info: strawberry.Info,
        input: RecordMessageDoneInput) -> RecordMessageDonePayload

Mark one chatter message done for the current user.

set_record_following

python
@strawberry.mutation(name="set_record_following")
def set_record_following(info: strawberry.Info,
                         input: RecordFollowInput) -> RecordFollowPayload

Follow or unfollow a record's chatter thread as the current user.

schedule_record_activity

python
@strawberry.mutation(name="schedule_record_activity")
def schedule_record_activity(
        info: strawberry.Info,
        input: RecordActivityScheduleInput) -> RecordActivityPayload

Schedule an activity on the record's chatter thread.

complete_record_activity

python
@strawberry.mutation(name="complete_record_activity")
def complete_record_activity(
        info: strawberry.Info,
        input: RecordActivityFeedbackInput) -> RecordActivityPayload

Complete a scheduled record activity and log its feedback.

cancel_record_activity

python
@strawberry.mutation(name="cancel_record_activity")
def cancel_record_activity(
        info: strawberry.Info,
        input: RecordActivityCancelInput) -> RecordActivityPayload

Cancel a scheduled record activity.

mark_record_thread_read

python
@strawberry.mutation(name="mark_record_thread_read")
def mark_record_thread_read(
        info: strawberry.Info,
        input: RecordReferenceInput) -> RecordThreadPayload

Mark the current user's notifications on a record thread as read.

_InboxWriteBackend

python
class _InboxWriteBackend(AngeeHasuraWriteBackend)

Write backend that keeps record-attached chatter off the generic mutations.

The read side scopes threads/messages to .inbox() through get_queryset; the write side must match, or a creator who lost record access could still reach a record-attached row through update_<res>_by_pk / delete_<res>_by_pk (its own owner/admin permission would allow it). Narrowing the write-target queryset to .inbox() makes the by-pk lookup miss a record-attached row, so the generic mutation reports it as not found while the inbox rows keep their update/delete surfaces. The .inbox() verb resolves polymorphically on each model's queryset, so one backend serves both.

write_target_queryset

python
def write_target_queryset() -> Any

Scope the update/delete/save target queryset to non-record rows.

_ChannelWriteBackend

python
class _ChannelWriteBackend(AngeeHasuraWriteBackend)

Make the generic delete_channels_by_pk a purge, not an orphan.

delete=True on the channel resource is what lights the console's delete button (the generic views gate on the presence of the delete root). Its default backend would SET_NULL the channel's threads/messages — the shared-Integration-parent orphan this whole feature exists to prevent — so the auto path routes through the same :meth:ChannelManager.purge as the authored delete_channel preview mutation the console actually drives; both are thin dispatchers to that one owner.

delete

python
def delete(info: strawberry.Info, pk: str) -> Any

Purge, then delete, one public-id-addressed channel; return the deleted row.

Released under the AGPL-3.0 License.