Header menu logo FSharp.ATProto

Notifications

Fetch, count, and mark notifications as read through the Bluesky module.

All examples use taskResult {} -- see the Error Handling guide for details.

Domain Types

Notification

A notification from the user's notification feed.

Field

Type

Description

RecordUri

AtUri

AT-URI of the notification record

Author

ProfileSummary

The user who triggered the notification

Content

NotificationContent

Rich discriminated union with type-specific data

IsRead

bool

Whether the notification has been seen

IndexedAt

DateTimeOffset

When the notification was indexed

NotificationContent

Rich discriminated union for notification types. Uses [<RequireQualifiedAccess>], so all cases must be qualified (e.g. NotificationContent.Like). Each case carries the data relevant to that notification kind.

Case

Fields

Description

NotificationContent.Like

post: PostRef

Someone liked your post

NotificationContent.Repost

post: PostRef

Someone reposted your post

NotificationContent.Follow

--

Someone followed you

NotificationContent.Reply

text: string * inReplyTo: PostRef

Someone replied to your post

NotificationContent.Mention

text: string

Someone mentioned you in a post

NotificationContent.Quote

text: string * quotedPost: PostRef

Someone quoted your post

NotificationContent.StarterpackJoined

starterPackUri: AtUri

Someone joined via your starter pack

NotificationContent.Unknown

reason: string

A notification type not yet recognized by the library

Functions

Reading

Function

Accepts

Returns

Description

Bluesky.getNotifications

agent, limit: int64 option, cursor: string option

Result<Page<Notification>, XrpcError>

Fetch a page of notifications

Bluesky.getUnreadNotificationCount

agent

Result<int64, XrpcError>

Get the number of unseen notifications

taskResult {
    let! count = Bluesky.getUnreadNotificationCount agent
    printfn "You have %d unread notifications" count
    ()
}
taskResult {
    let! page = Bluesky.getNotifications agent (Some 25L) None

    for n in page.Items do
        match n.Content with
        | NotificationContent.Like postRef ->
            printfn "%s liked your post (%O)" n.Author.DisplayName postRef.Uri
        | NotificationContent.Follow ->
            printfn "%s followed you" n.Author.DisplayName
        | NotificationContent.Reply (text, _) ->
            printfn "%s replied: %s" n.Author.DisplayName text
        | NotificationContent.Mention text ->
            printfn "%s mentioned you: %s" n.Author.DisplayName text
        | NotificationContent.Repost postRef ->
            printfn "%s reposted (%O)" n.Author.DisplayName postRef.Uri
        | NotificationContent.Quote (text, _) ->
            printfn "%s quoted you: %s" n.Author.DisplayName text
        | _ -> printfn "Other notification from %s" n.Author.DisplayName
}

Actions

Function

Accepts

Returns

Description

Bluesky.markNotificationsSeen

agent

Result<unit, XrpcError>

Mark all notifications as seen up to the current time

The protocol treats "seen" as a high-water mark -- there is no way to mark individual notifications. All notifications up to the current timestamp are marked as seen.

taskResult {
    do! Bluesky.markNotificationsSeen agent
}

Pagination

Function

Accepts

Returns

Description

Bluesky.paginateNotifications

agent, pageSize: int64 option

IAsyncEnumerable<Result<Page<Notification>, XrpcError>>

Lazily paginate all notifications

The paginator returns an IAsyncEnumerable that fetches pages on demand and stops when the server has no more results:

task {
    let pages = Bluesky.paginateNotifications agent (Some 50L)

    let enumerator = pages.GetAsyncEnumerator()

    let rec loop () = task {
        let! hasNext = enumerator.MoveNextAsync()
        if hasNext then
            match enumerator.Current with
            | Ok page ->
                for n in page.Items do
                    printfn "%s: %A" n.Author.DisplayName n.Content
            | Error err ->
                printfn "Error: %A" err
            do! loop ()
    }

    do! loop ()
    do! enumerator.DisposeAsync()
}

See the Pagination guide for more patterns on consuming IAsyncEnumerable from F#.

Complete Workflow

A typical notification check: read the unread count, fetch if there are any, process them, then mark as seen.

open FSharp.ATProto.Core
open FSharp.ATProto.Bluesky

taskResult {
    let! agent = Bluesky.login "https://bsky.social" "handle.bsky.social" "app-password"

    let! unread = Bluesky.getUnreadNotificationCount agent

    if unread > 0L then
        let! page = Bluesky.getNotifications agent (Some 50L) None

        for n in page.Items do
            if not n.IsRead then
                match n.Content with
                | NotificationContent.Follow ->
                    printfn "New follower: %s (%O)" n.Author.DisplayName n.Author.Handle
                | NotificationContent.Like postRef
                | NotificationContent.Repost postRef ->
                    printfn "%s interacted with %O" n.Author.DisplayName postRef.Uri
                | NotificationContent.Reply (text, _) ->
                    printfn "%s replied: %s" n.Author.DisplayName text
                | NotificationContent.Mention text ->
                    printfn "%s mentioned you: %s" n.Author.DisplayName text
                | NotificationContent.Quote (text, _) ->
                    printfn "%s quoted you: %s" n.Author.DisplayName text
                | _ -> ()

        do! Bluesky.markNotificationsSeen agent
        printfn "Marked %d notifications as seen" unread
    else
        printfn "No new notifications"
}

Power Users: Raw XRPC

For fields the Notification domain type does not expose (such as Labels or the raw Record JSON), drop to the generated XRPC wrapper:

taskResult {
    let! output =
        AppBskyNotification.ListNotifications.query agent
            { Cursor = None
              Limit = Some 10L
              Priority = None
              Reasons = None
              SeenAt = None }

    for n in output.Notifications do
        printfn "%O (%A) - labels: %A" n.Uri n.Reason n.Labels
}
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.ATProto
namespace FSharp.ATProto.Syntax
namespace FSharp.ATProto.Core
namespace FSharp.ATProto.Bluesky
val agent: AtpAgent
module Unchecked from Microsoft.FSharp.Core.Operators
val defaultof<'T> : 'T
Multiple items
module AtpAgent from FSharp.ATProto.Core
<summary> Functions for creating and authenticating <see cref="AtpAgent" /> instances. </summary>

--------------------
type AtpAgent = { HttpClient: HttpClient mutable BaseUrl: Uri mutable Session: AtpSession option ExtraHeaders: (string * string) list AuthenticateRequest: (HttpRequestMessage -> unit) option RefreshAuthentication: (unit -> Task<Result<unit,XrpcError>>) option OnSessionChanged: (unit -> unit) option }
<summary> Client agent for communicating with an AT Protocol Personal Data Server (PDS). Holds the HTTP client, base URL, optional authenticated session, and extra headers. </summary>
<remarks> Create an agent with <see cref="AtpAgent.create" /> or <see cref="AtpAgent.createWithClient" />, then authenticate with <see cref="AtpAgent.login" />. The agent's <see cref="Session" /> field is mutable: it is updated automatically on login and token refresh. </remarks>
<example><code> let agent = AtpAgent.create "https://bsky.social" let! session = AtpAgent.login "my-handle.bsky.social" "app-password-here" agent </code></example>
val taskResult: TaskResultBuilder
val count: int64
module Bluesky from FSharp.ATProto.Bluesky
<summary> High-level convenience methods for common Bluesky operations: posting, replying, liking, reposting, following, blocking, uploading blobs, and deleting records. All methods require an authenticated <see cref="AtpAgent" />. </summary>
val getUnreadNotificationCount: agent: AtpAgent -> System.Threading.Tasks.Task<Result<int64,XrpcError>>
<summary> Get the count of unread notifications for the authenticated user. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<returns>The unread notification count on success, or an <see cref="XrpcError" />.</returns>
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
val page: Page<Notification>
val getNotifications: agent: AtpAgent -> limit: int64 option -> cursor: string option -> System.Threading.Tasks.Task<Result<Page<Notification>,XrpcError>>
<summary> List notifications for the authenticated user. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="limit">Maximum number of notifications to return (optional, pass <c>None</c> for server default).</param>
<param name="cursor">Pagination cursor from a previous response (optional, pass <c>None</c> to start from the beginning).</param>
<returns>A page of <see cref="Notification" /> with an optional cursor, or an <see cref="XrpcError" />.</returns>
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val n: Notification
Page.Items: Notification list
Notification.Content: NotificationContent
type NotificationContent = | Like of post: PostRef | Repost of post: PostRef | Follow | Reply of text: string * inReplyTo: PostRef | Mention of text: string | Quote of text: string * quotedPost: PostRef | StarterpackJoined of starterPackUri: AtUri | Unknown of reason: string
<summary>The content of a notification, varying by kind.</summary>
union case NotificationContent.Like: post: PostRef -> NotificationContent
val postRef: PostRef
Notification.Author: ProfileSummary
ProfileSummary.DisplayName: string
PostRef.Uri: AtUri
<summary>The AT-URI of the post record.</summary>
union case NotificationContent.Follow: NotificationContent
union case NotificationContent.Reply: text: string * inReplyTo: PostRef -> NotificationContent
val text: string
union case NotificationContent.Mention: text: string -> NotificationContent
union case NotificationContent.Repost: post: PostRef -> NotificationContent
union case NotificationContent.Quote: text: string * quotedPost: PostRef -> NotificationContent
val markNotificationsSeen: agent: AtpAgent -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Mark all notifications as seen up to the current time. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
val task: TaskBuilder
val pages: System.Collections.Generic.IAsyncEnumerable<Result<Page<Notification>,XrpcError>>
val paginateNotifications: agent: AtpAgent -> pageSize: int64 option -> System.Collections.Generic.IAsyncEnumerable<Result<Page<Notification>,XrpcError>>
<summary> Paginate notifications for the authenticated user. Returns an async enumerable of pages. Each element is a <c>Result</c> containing one page of notifications. Pagination stops automatically when the server returns no cursor. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="pageSize">Maximum number of notifications per page (optional, pass <c>None</c> for server default).</param>
<returns>An <see cref="System.Collections.Generic.IAsyncEnumerable{T}" /> of paginated results.</returns>
val enumerator: System.Collections.Generic.IAsyncEnumerator<Result<Page<Notification>,XrpcError>>
System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(?cancellationToken: System.Threading.CancellationToken) : System.Collections.Generic.IAsyncEnumerator<Result<Page<Notification>,XrpcError>>
val loop: unit -> System.Threading.Tasks.Task<unit>
val hasNext: bool
System.Collections.Generic.IAsyncEnumerator.MoveNextAsync() : System.Threading.Tasks.ValueTask<bool>
property System.Collections.Generic.IAsyncEnumerator.Current: Result<Page<Notification>,XrpcError> with get
<summary>Gets the element in the collection at the current position of the enumerator.</summary>
<returns>The element in the collection at the current position of the enumerator.</returns>
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
val err: XrpcError
System.IAsyncDisposable.DisposeAsync() : System.Threading.Tasks.ValueTask
val login: baseUrl: string -> identifier: string -> password: string -> System.Threading.Tasks.Task<Result<AtpAgent,XrpcError>>
val unread: int64
Notification.IsRead: bool
ProfileSummary.Handle: Handle
val output: AppBskyNotification.ListNotifications.Output
module AppBskyNotification from FSharp.ATProto.Bluesky
module ListNotifications from FSharp.ATProto.Bluesky.AppBskyNotification
val query: agent: AtpAgent -> parameters: AppBskyNotification.ListNotifications.Params -> System.Threading.Tasks.Task<Result<AppBskyNotification.ListNotifications.Output,XrpcError>>
val n: AppBskyNotification.ListNotifications.Notification
AppBskyNotification.ListNotifications.Output.Notifications: AppBskyNotification.ListNotifications.Notification list
AppBskyNotification.ListNotifications.Notification.Uri: AtUri
AppBskyNotification.ListNotifications.Notification.Reason: AppBskyNotification.ListNotifications.NotificationReason
AppBskyNotification.ListNotifications.Notification.Labels: ComAtprotoLabel.Defs.Label list option

Type something to start searching.