diff --git a/go.mod b/go.mod
index b710b86a89f37065ea2157917da8bd5622bf95d4..c770d5c5c3a78504aedcb5db14503515f249a9e2 100644
--- a/go.mod
+++ b/go.mod
@@ -10,11 +10,11 @@ require (
 	github.com/google/go-querystring v1.1.0
 	github.com/google/uuid v1.6.0
 	github.com/stretchr/testify v1.10.0
-	go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003
-	golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c
-	golang.org/x/net v0.34.0
+	go.mau.fi/util v0.8.5
+	golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
+	golang.org/x/net v0.35.0
 	gopkg.in/yaml.v3 v3.0.1
-	maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91
+	maunium.net/go/mautrix v0.23.1
 )
 
 require (
@@ -28,7 +28,7 @@ require (
 	github.com/mattn/go-colorable v0.1.14 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-sqlite3 v1.14.24 // indirect
-	github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 // indirect
+	github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rs/xid v1.6.0 // indirect
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
@@ -38,10 +38,10 @@ require (
 	github.com/tidwall/sjson v1.2.5 // indirect
 	github.com/yuin/goldmark v1.7.8 // indirect
 	go.mau.fi/zeroconfig v0.1.3 // indirect
-	golang.org/x/crypto v0.32.0 // indirect
-	golang.org/x/sync v0.10.0 // indirect
-	golang.org/x/sys v0.29.0 // indirect
-	golang.org/x/text v0.21.0 // indirect
+	golang.org/x/crypto v0.33.0 // indirect
+	golang.org/x/sync v0.11.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
 	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	maunium.net/go/mauflag v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index a33905aa89f7af776c03a238d6b9d3f3ff01d3f3..7bc9b97772f0f12fe20843b424e8e56cd515f9e8 100644
--- a/go.sum
+++ b/go.sum
@@ -34,8 +34,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
 github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
-github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 h1:ah1dvbqPMN5+ocrg/ZSgZ6k8bOk+kcZQ7fnyx6UvOm4=
-github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
+github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a h1:ckxP/kGzsxvxXo8jO6E/0QJ8MMmwI7IRj4Fys9QbAZA=
+github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -60,25 +60,25 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
 github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003 h1:ye5l+QpYW5CpGVMedb3EHlmflGMQsMtw8mC4K/U8hIw=
-go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
+go.mau.fi/util v0.8.5 h1:PwCAAtcfK0XxZ4sdErJyfBMkTEWoQU33aB7QqDDzQRI=
+go.mau.fi/util v0.8.5/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M=
 go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
 go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
-golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
-golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
-golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
-golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
-golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
-golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
+golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
+golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
+golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
-golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
@@ -89,5 +89,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
 maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
-maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91 h1:jbga2dSYVTd3MgAKugiz5+mIYp+qxUOCDokUGZOEWRg=
-maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91/go.mod h1:q2U2IRLSFpglDhIpSjd8TnCNVzBNrUJBD8pmYCGQiwc=
+maunium.net/go/mautrix v0.23.1 h1:xZtX43YZF3WRxkdR+oMUrpiQe+jbjc+LeXLxHuXP5IM=
+maunium.net/go/mautrix v0.23.1/go.mod h1:kldoZQDneV/jquIhwG1MmMw5j2A2M/MnQYRSWt863cY=
diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go
index d81949639b9071ed2bc3634ba4ba120ca34183f3..afdee2c948ddc2d92a709fdf55ca0a19daa6faa3 100644
--- a/pkg/connector/backfill.go
+++ b/pkg/connector/backfill.go
@@ -2,11 +2,18 @@ package connector
 
 import (
 	"context"
+	"fmt"
 
 	"maunium.net/go/mautrix/bridgev2"
+
+	"go.mau.fi/mautrix-linkedin/pkg/linkedingo"
 )
 
 func (l *LinkedInClient) FetchMessages(ctx context.Context, fetchParams bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) {
+	if messages, ok := fetchParams.BundledData.([]linkedingo.Message); ok {
+		fmt.Printf("%+v\n", messages)
+	}
+
 	// variables := queryold.FetchMessagesVariables{
 	// 	ConversationURN: linkedingo.NewURN(fetchParams.Portal.ID),
 	// 	CountBefore:     20,
diff --git a/pkg/connector/client.go b/pkg/connector/client.go
index ba4fc361cdbf658ba31328a4cc9f9277e2e534fc..a8e16a0c49939d345f277c71dcfd13d99d6f1621 100644
--- a/pkg/connector/client.go
+++ b/pkg/connector/client.go
@@ -309,22 +309,35 @@ func (l *LinkedInClient) onRealtimeReactionSummaries(ctx context.Context, summar
 	})
 }
 
-func (l *LinkedInClient) getAvatar(img *linkedingo.VectorImage) (avatar bridgev2.Avatar) {
-	avatar.ID = networkid.AvatarID(img.RootURL)
-	avatar.Remove = img.RootURL == ""
-	avatar.Get = func(ctx context.Context) ([]byte, error) {
-		return l.client.DownloadBytes(ctx, img.GetLargestArtifactURL())
+func (l *LinkedInClient) getAvatar(img *linkedingo.VectorImage) (avatar *bridgev2.Avatar) {
+	if img == nil {
+		return nil
+	}
+	return &bridgev2.Avatar{
+		ID:     networkid.AvatarID(img.RootURL),
+		Remove: img.RootURL == "",
+		Get: func(ctx context.Context) ([]byte, error) {
+			return l.client.DownloadBytes(ctx, img.GetLargestArtifactURL())
+		},
 	}
-	return
 }
 
 func (l *LinkedInClient) getMessagingParticipantUserInfo(participant linkedingo.MessagingParticipant) (ui bridgev2.UserInfo) {
-	ui.Name = ptr.Ptr(l.main.Config.FormatDisplayname(DisplaynameParams{
-		FirstName: participant.ParticipantType.Member.FirstName.Text,
-		LastName:  participant.ParticipantType.Member.LastName.Text,
-	}))
-	ui.Avatar = ptr.Ptr(l.getAvatar(participant.ParticipantType.Member.ProfilePicture))
-	ui.Identifiers = []string{fmt.Sprintf("linkedin:%s", participant.EntityURN.ID())}
+	switch {
+	case participant.ParticipantType.Member != nil:
+		ui.Name = ptr.Ptr(l.main.Config.FormatDisplayname(DisplaynameParams{
+			FirstName: participant.ParticipantType.Member.FirstName.Text,
+			LastName:  participant.ParticipantType.Member.LastName.Text,
+		}))
+		ui.Avatar = l.getAvatar(participant.ParticipantType.Member.ProfilePicture)
+		ui.Identifiers = []string{fmt.Sprintf("linkedin:%s", participant.EntityURN.ID())}
+	case participant.ParticipantType.Organization != nil:
+		ui.Name = ptr.Ptr(l.main.Config.FormatDisplayname(DisplaynameParams{
+			Organization: participant.ParticipantType.Organization.Name.Text,
+		}))
+		ui.Avatar = l.getAvatar(participant.ParticipantType.Organization.Logo)
+		ui.Identifiers = []string{fmt.Sprintf("linkedin:%s", participant.EntityURN.ID())}
+	}
 	return
 }
 
diff --git a/pkg/connector/config.go b/pkg/connector/config.go
index 05fadcaaef47849d392eb54b608b6f2a85583f83..b79f1ae694c6e1c675b5a71139af362346be2c26 100644
--- a/pkg/connector/config.go
+++ b/pkg/connector/config.go
@@ -57,8 +57,9 @@ func (lc *LinkedInConnector) GetConfig() (string, any, up.Upgrader) {
 }
 
 type DisplaynameParams struct {
-	FirstName string
-	LastName  string
+	FirstName    string
+	LastName     string
+	Organization string
 }
 
 func (c *Config) FormatDisplayname(params DisplaynameParams) string {
diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml
index 8d999c9b229329195fd42c1af13d2da97eb758be..886991226772df9cb2480221cc805badaf654093 100644
--- a/pkg/connector/example-config.yaml
+++ b/pkg/connector/example-config.yaml
@@ -1,4 +1,4 @@
 # Displayname template for LinkedIn users.
 # .FirstName is replaced with the first name
 # .LastName is replaced with the last name
-displayname_template: "{{ .FirstName }} {{ .LastName }} (LinkedIn)"
+displayname_template: "{{ with .Organization }}{{ . }}{{ else }}{{ .FirstName }} {{ .LastName }}{{ end }} (LinkedIn)"
diff --git a/pkg/connector/linkedinfmt/convert.go b/pkg/connector/linkedinfmt/convert.go
index 942a533f94fff22fe67916ac5a12d0ef95ebe971..a76332bd24cc6637d5dfc6f6a9c7aa948d5d6a33 100644
--- a/pkg/connector/linkedinfmt/convert.go
+++ b/pkg/connector/linkedinfmt/convert.go
@@ -70,6 +70,8 @@ func Parse(ctx context.Context, message string, attributes []linkedingo.Attribut
 			Length: a.Length,
 		}.TruncateEnd(maxLength)
 		switch {
+		case a.AttributeKind.Bold != nil:
+			br.Value = Style{Type: StyleBold}
 		case a.AttributeKind.Entity != nil:
 			urn := a.AttributeKind.Entity.URN
 			var userInfo UserInfo
@@ -83,8 +85,26 @@ func Parse(ctx context.Context, message string, attributes []linkedingo.Attribut
 			userInfo.Name = utf16Message[a.Start+1 : a.Start+a.Length].String()
 			mentions[userInfo.MXID] = struct{}{}
 			br.Value = Mention{userInfo, networkid.UserID(urn.ID())}
+		case a.AttributeKind.Hyperlink != nil:
+			br.Value = Style{Type: StyleHyperlink, URL: a.AttributeKind.Hyperlink.URL}
+		case a.AttributeKind.Italic != nil:
+			br.Value = Style{Type: StyleItalic}
+		case a.AttributeKind.LineBreak != nil:
+			br.Value = Style{Type: StyleLineBreak}
+		case a.AttributeKind.List != nil:
+			br.Value = Style{Type: StyleList, Ordered: a.AttributeKind.List.Ordered}
+		case a.AttributeKind.ListItem != nil:
+			br.Value = Style{Type: StyleListItem}
+		case a.AttributeKind.Paragraph != nil:
+			br.Value = Style{Type: StyleParagraph}
+		case a.AttributeKind.Subscript != nil:
+			br.Value = Style{Type: StyleSubscript}
+		case a.AttributeKind.Superscript != nil:
+			br.Value = Style{Type: StyleSuperscript}
+		case a.AttributeKind.Underline != nil:
+			br.Value = Style{Type: StyleUnderline}
 		default:
-			log.Warn().Msg("Unhandled attribute")
+			log.Warn().Any("kind", a.AttributeKind).Msg("Unhandled attribute")
 		}
 		lrt.Add(br)
 	}
diff --git a/pkg/connector/linkedinfmt/html.go b/pkg/connector/linkedinfmt/html.go
index 3501a83249872f95d13a20ba1452e248dab69824..d9e10c810ab7683e715065cec1a16b45905e329c 100644
--- a/pkg/connector/linkedinfmt/html.go
+++ b/pkg/connector/linkedinfmt/html.go
@@ -32,46 +32,29 @@ func (s Style) Format(message string) string {
 		return fmt.Sprintf("<strong>%s</strong>", message)
 	case StyleItalic:
 		return fmt.Sprintf("<em>%s</em>", message)
-	case StyleSpoiler:
-		return fmt.Sprintf("<span data-mx-spoiler>%s</span>", message)
-	case StyleStrikethrough:
-		return fmt.Sprintf("<del>%s</del>", message)
-	case StyleCode:
-		if strings.ContainsRune(message, '\n') {
-			// This is somewhat incorrect, as it won't allow inline text before/after a multiline monospace-formatted string.
-			return fmt.Sprintf("<pre><code>%s</code></pre>", message)
-		}
-		return fmt.Sprintf("<code>%s</code>", message)
-	case StyleUnderline:
-		return fmt.Sprintf("<u>%s</u>", message)
-	case StyleBlockquote:
-		return fmt.Sprintf("<blockquote>%s</blockquote>", message)
-	case StylePre:
-		if s.Language != "" {
-			return fmt.Sprintf("<pre><code class='language-%s'>%s</code></pre>", s.Language, message)
+	case StyleLineBreak:
+		return "<br>"
+	case StyleList:
+		if s.Ordered {
+			return fmt.Sprintf("<ol>%s</ol>", message)
 		} else {
-			return fmt.Sprintf("<pre><code>%s</code></pre>", message)
-		}
-	case StyleEmail:
-		return fmt.Sprintf(`<a href='mailto:%s'>%s</a>`, message, message)
-	case StyleTextURL:
-		if strings.HasPrefix(s.URL, "https://matrix.to/#") {
-			return s.URL
+			return fmt.Sprintf("<ul>%s</ul>", message)
 		}
-		return fmt.Sprintf(`<a href='%s'>%s</a>`, s.URL, message)
-	case StyleURL:
+	case StyleListItem:
+		return fmt.Sprintf("<li>%s</li>", message)
+	case StyleParagraph:
+		return fmt.Sprintf("<p>%s</p>", message)
+	case StyleSubscript:
+		return fmt.Sprintf("<sub>%s</sub>", message)
+	case StyleSuperscript:
+		return fmt.Sprintf("<sup>%s</sup>", message)
+	case StyleHyperlink:
 		if strings.HasPrefix(s.URL, "https://matrix.to/#") {
 			return s.URL
 		}
 		return fmt.Sprintf(`<a href='%s'>%s</a>`, s.URL, message)
-	case StyleBotCommand:
-		return fmt.Sprintf("<font color='#3771bb'>%s</font>", message)
-	case StyleHashtag:
-		return fmt.Sprintf("<font color='#3771bb'>%s</font>", message)
-	case StyleCashtag:
-		return fmt.Sprintf("<font color='#3771bb'>%s</font>", message)
-	case StylePhone:
-		return fmt.Sprintf("<font color='#3771bb'>%s</font>", message)
+	case StyleUnderline:
+		return fmt.Sprintf("<u>%s</u>", message)
 	default:
 		return message
 	}
@@ -96,9 +79,6 @@ func (lrt *LinkedRangeTree) Format(message UTF16String, ctx formatContext) strin
 	inner := message[lrt.Node.Start:lrt.Node.End()]
 	tail := message[lrt.Node.End():]
 	ourCtx := ctx
-	if lrt.Node.Value.IsCode() {
-		ourCtx.IsInCodeblock = true
-	}
 	childMessage := lrt.Child.Format(inner, ourCtx)
 	formattedChildMessage := lrt.Node.Value.Format(childMessage)
 	siblingMessage := lrt.Sibling.Format(tail, ctx)
diff --git a/pkg/connector/linkedinfmt/styletype_string.go b/pkg/connector/linkedinfmt/styletype_string.go
new file mode 100644
index 0000000000000000000000000000000000000000..502405f6f52a488bbf7b49524d6a40eef8ce5b2b
--- /dev/null
+++ b/pkg/connector/linkedinfmt/styletype_string.go
@@ -0,0 +1,33 @@
+// Code generated by "stringer -type=StyleType"; DO NOT EDIT.
+
+package linkedinfmt
+
+import "strconv"
+
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[StyleNone-0]
+	_ = x[StyleBold-1]
+	_ = x[StyleItalic-2]
+	_ = x[StyleLineBreak-3]
+	_ = x[StyleList-4]
+	_ = x[StyleListItem-5]
+	_ = x[StyleParagraph-6]
+	_ = x[StyleSubscript-7]
+	_ = x[StyleSuperscript-8]
+	_ = x[StyleHyperlink-9]
+	_ = x[StyleUnderline-10]
+}
+
+const _StyleType_name = "StyleNoneStyleBoldStyleItalicStyleLineBreakStyleListStyleListItemStyleParagraphStyleSubscriptStyleSuperscriptStyleHyperlinkStyleUnderline"
+
+var _StyleType_index = [...]uint8{0, 9, 18, 29, 43, 52, 65, 79, 93, 109, 123, 137}
+
+func (i StyleType) String() string {
+	if i < 0 || i >= StyleType(len(_StyleType_index)-1) {
+		return "StyleType(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _StyleType_name[_StyleType_index[i]:_StyleType_index[i+1]]
+}
diff --git a/pkg/connector/linkedinfmt/tags.go b/pkg/connector/linkedinfmt/tags.go
index e81afc0207d49f973e5d237e11749c0a0feb049f..7736c95572aca61216b2a1471c841ab55ab79297 100644
--- a/pkg/connector/linkedinfmt/tags.go
+++ b/pkg/connector/linkedinfmt/tags.go
@@ -20,13 +20,11 @@ import (
 	"fmt"
 
 	"maunium.net/go/mautrix/bridgev2/networkid"
-	"maunium.net/go/mautrix/id"
 )
 
 type BodyRangeValue interface {
 	String() string
 	Format(message string) string
-	IsCode() bool
 }
 
 type Mention struct {
@@ -40,97 +38,37 @@ func (m Mention) String() string {
 	return fmt.Sprintf("Mention{MXID: id.UserID(%q), Name: %q}", m.MXID, m.Name)
 }
 
-func (m Mention) IsCode() bool {
-	return false
-}
-
+//go:generate stringer -type=StyleType
 type StyleType int
 
-var _ BodyRangeValue = Mention{}
-
 const (
 	StyleNone StyleType = iota
 	StyleBold
 	StyleItalic
+	StyleLineBreak
+	StyleList
+	StyleListItem
+	StyleParagraph
+	StyleSubscript
+	StyleSuperscript
+	StyleHyperlink
 	StyleUnderline
-	StyleStrikethrough
-	StyleBlockquote
-	StyleCode
-	StylePre
-	StyleEmail
-	StyleTextURL
-	StyleURL
-	StyleBotCommand
-	StyleHashtag
-	StyleCashtag
-	StylePhone
-	StyleSpoiler
-	StyleBankCard
 )
 
-func (s StyleType) String() string {
-	switch s {
-	case StyleNone:
-		return "StyleNone"
-	case StyleBold:
-		return "StyleBold"
-	case StyleItalic:
-		return "StyleItalic"
-	case StyleUnderline:
-		return "StyleUnderline"
-	case StyleStrikethrough:
-		return "StyleStrikethrough"
-	case StyleBlockquote:
-		return "StyleBlockquote"
-	case StyleCode:
-		return "StyleCode"
-	case StylePre:
-		return "StylePre"
-	case StyleEmail:
-		return "StyleEmail"
-	case StyleTextURL:
-		return "StyleTextURL"
-	case StyleURL:
-		return "StyleEntityURL"
-	case StyleBotCommand:
-		return "StyleBotCommand"
-	case StyleHashtag:
-		return "StyleHashtag"
-	case StyleCashtag:
-		return "StyleCashtag"
-	case StylePhone:
-		return "StylePhone"
-	case StyleSpoiler:
-		return "StyleSpoiler"
-	case StyleBankCard:
-		return "StyleBankCard"
-	default:
-		return fmt.Sprintf("StyleType(%d)", s)
-	}
-}
-
 // Style represents a style to apply to a range of text.
 type Style struct {
 	// Type is the type of style.
 	Type StyleType
 
-	// Language is the language of the code block, if applicable.
-	Language string
+	// Ordered indicates whether the list is ordered.
+	Ordered bool
 
 	// URL is the URL to link to, if applicable.
 	URL string
-
-	// Emoji is the emoji to display, if applicable.
-	Emoji string
-
-	// EmojiURI is the URI to the emoji, if applicable.
-	EmojiURI id.ContentURIString
 }
 
-func (s Style) String() string {
-	return fmt.Sprintf("Style{Type: %s, Language: %s, URL: %s}", s.Type, s.Language, s.URL)
-}
+var _ BodyRangeValue = Style{}
 
-func (s Style) IsCode() bool {
-	return s.Type == StyleCode || s.Type == StylePre
+func (s Style) String() string {
+	return fmt.Sprintf("Style{Type: %s, URL: %s}", s.Type, s.URL)
 }
diff --git a/pkg/connector/matrixfmt/convert.go b/pkg/connector/matrixfmt/convert.go
index 0c696d5d06e3ec5f0f89e4f867aaa68e38d62921..97162ba86646b5341795fb99a74f1ad48bcaa663 100644
--- a/pkg/connector/matrixfmt/convert.go
+++ b/pkg/connector/matrixfmt/convert.go
@@ -38,9 +38,36 @@ func toLinkedInAttribute(br linkedinfmt.BodyRange) linkedingo.SendMessageAttribu
 			},
 		}
 	case linkedinfmt.Style:
+		// TODO this doesn't seem to work, LinkedIn seems to ignore these
+		attributeKind := linkedingo.AttributeKind{}
 		switch val.Type {
+		case linkedinfmt.StyleBold:
+			attributeKind.Bold = &linkedingo.Bold{}
+		case linkedinfmt.StyleItalic:
+			attributeKind.Italic = &linkedingo.Italic{}
+		case linkedinfmt.StyleLineBreak:
+			attributeKind.LineBreak = &linkedingo.LineBreak{}
+		case linkedinfmt.StyleList:
+			attributeKind.List = &linkedingo.List{Ordered: val.Ordered}
+		case linkedinfmt.StyleListItem:
+			attributeKind.ListItem = &linkedingo.ListItem{}
+		case linkedinfmt.StyleParagraph:
+			attributeKind.Paragraph = &linkedingo.Paragraph{}
+		case linkedinfmt.StyleSubscript:
+			attributeKind.Subscript = &linkedingo.Subscript{}
+		case linkedinfmt.StyleSuperscript:
+			attributeKind.Superscript = &linkedingo.Superscript{}
+		case linkedinfmt.StyleHyperlink:
+			attributeKind.Hyperlink = &linkedingo.Hyperlink{URL: val.URL}
+		case linkedinfmt.StyleUnderline:
+			attributeKind.Underline = &linkedingo.Underline{}
 		default:
-			panic("unsupported style type")
+			panic("unsupported style type %s")
+		}
+		return linkedingo.SendMessageAttribute{
+			Start:              br.Start,
+			Length:             br.Length,
+			AttributeKindUnion: attributeKind,
 		}
 	default:
 		panic("unknown body range value")
diff --git a/pkg/connector/matrixfmt/html.go b/pkg/connector/matrixfmt/html.go
index 1717d5d9c996e2fcc701ffa94cd762d6df58efe2..318daa20eb252d6c88c5ae24f3ad0a4eb1d9c0c2 100644
--- a/pkg/connector/matrixfmt/html.go
+++ b/pkg/connector/matrixfmt/html.go
@@ -18,9 +18,7 @@ package matrixfmt
 
 import (
 	"context"
-	"fmt"
 	"math"
-	"strconv"
 	"strings"
 
 	"golang.org/x/exp/slices"
@@ -282,53 +280,6 @@ func Digits(num int) int {
 	return int(math.Floor(math.Log10(float64(num))) + 1)
 }
 
-var listBullets = []string{"●", "○", "■", "‣"}
-
-func (parser *HTMLParser) listToString(node *html.Node, ctx Context) *EntityString {
-	ordered := node.Data == "ol"
-	if !ordered {
-		ctx = ctx.WithIncrementedListDepth()
-	}
-	taggedChildren := parser.nodeToTaggedStrings(node.FirstChild, ctx)
-	counter := 1
-	indentLength := 0
-	if ordered {
-		start := parser.getAttribute(node, "start")
-		if len(start) > 0 {
-			counter, _ = strconv.Atoi(start)
-		}
-
-		longestIndex := (counter - 1) + len(taggedChildren)
-		indentLength = Digits(longestIndex)
-	}
-	indent := strings.Repeat(" ", indentLength+2)
-	var children []*EntityString
-	for _, child := range taggedChildren {
-		if child.tag != "li" {
-			continue
-		}
-		var prefix string
-		if ordered {
-			indexPadding := indentLength - Digits(counter)
-			if indexPadding < 0 {
-				// This will happen on negative start indexes where longestIndex is usually wrong, otherwise shouldn't happen
-				indexPadding = 0
-			}
-			prefix = fmt.Sprintf("%d. %s", counter, strings.Repeat(" ", indexPadding))
-		} else {
-			prefix = fmt.Sprintf("%s ", listBullets[(ctx.ListDepth-1)%len(listBullets)])
-		}
-		es := NewEntityString(prefix).Append(child.EntityString)
-		counter++
-		parts := es.Split('\n')
-		for i, part := range parts[1:] {
-			parts[i+1] = NewEntityString(indent).Append(part)
-		}
-		children = append(children, parts...)
-	}
-	return JoinEntityString("\n", children...)
-}
-
 func (parser *HTMLParser) basicFormatToString(node *html.Node, ctx Context) *EntityString {
 	str := parser.nodeToTagAwareString(node.FirstChild, ctx)
 	switch node.Data {
@@ -336,23 +287,22 @@ func (parser *HTMLParser) basicFormatToString(node *html.Node, ctx Context) *Ent
 		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleBold})
 	case "i", "em":
 		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleItalic})
-	case "s", "del", "strike":
-		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleStrikethrough})
-	case "u", "ins":
+	case "br":
+		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleLineBreak})
+	case "ul":
+		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleList, Ordered: false})
+	case "ol":
+		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleList, Ordered: true})
+	case "li":
+		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleListItem})
+	case "p":
+		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleParagraph})
+	case "sub":
+		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleSubscript})
+	case "sup":
+		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleSuperscript})
+	case "u":
 		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleUnderline})
-	case "tt", "code":
-		return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleCode})
-	}
-	return str
-}
-
-func (parser *HTMLParser) spanToString(node *html.Node, ctx Context) *EntityString {
-	str := parser.nodeToTagAwareString(node.FirstChild, ctx)
-	if node.Data == "span" {
-		_, isSpoiler := parser.maybeGetAttribute(node, "data-mx-spoiler")
-		if isSpoiler {
-			str = str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleSpoiler})
-		}
 	}
 	return str
 }
@@ -386,49 +336,26 @@ func (parser *HTMLParser) linkToString(node *html.Node, ctx Context) *EntityStri
 			return NewEntityString("@" + username).Format(linkedinfmt.Mention{UserID: userID})
 		}
 	}
-	if str.String.String() == href {
-		return ent.Format(linkedinfmt.Style{Type: linkedinfmt.StyleURL, URL: href})
-	} else {
-		return ent.Format(linkedinfmt.Style{Type: linkedinfmt.StyleTextURL, URL: href})
-	}
+	return ent.Format(linkedinfmt.Style{Type: linkedinfmt.StyleHyperlink, URL: href})
 }
 
 func (parser *HTMLParser) tagToString(node *html.Node, ctx Context) *EntityString {
 	ctx = ctx.WithTag(node.Data)
 	switch node.Data {
 	case "blockquote":
-		return parser.
-			nodeToTagAwareString(node.FirstChild, ctx).
-			Format(linkedinfmt.Style{Type: linkedinfmt.StyleBlockquote})
-	case "ol", "ul":
-		return parser.listToString(node, ctx)
+		return NewEntityString("> ").Append(parser.nodeToString(node.FirstChild, ctx))
 	case "h1", "h2", "h3", "h4", "h5", "h6":
 		return parser.headerToString(node, ctx)
 	case "br":
 		return NewEntityString("\n")
-	case "b", "strong", "i", "em", "s", "strike", "del", "u", "ins", "tt", "code":
+	case "b", "strong", "i", "em", "s", "strike", "del", "u", "ins", "tt", "code", "ol", "ul", "li":
 		return parser.basicFormatToString(node, ctx)
-	case "span", "font":
-		return parser.spanToString(node, ctx)
 	case "a":
 		return parser.linkToString(node, ctx)
 	case "p":
 		return parser.nodeToTagAwareString(node.FirstChild, ctx)
 	case "hr":
 		return NewEntityString("---")
-	case "pre":
-		var preStr *EntityString
-		var language string
-		if node.FirstChild != nil && node.FirstChild.Type == html.ElementNode && node.FirstChild.Data == "code" {
-			class := parser.getAttribute(node.FirstChild, "class")
-			if strings.HasPrefix(class, "language-") {
-				language = class[len("language-"):]
-			}
-			preStr = parser.nodeToString(node.FirstChild.FirstChild, ctx.WithWhitespace())
-		} else {
-			preStr = parser.nodeToString(node.FirstChild, ctx.WithWhitespace())
-		}
-		return preStr.Format(linkedinfmt.Style{Type: linkedinfmt.StylePre, Language: language})
 	default:
 		return parser.nodeToTagAwareString(node.FirstChild, ctx)
 	}
diff --git a/pkg/connector/sync.go b/pkg/connector/sync.go
index 7152d8f96dd81deee68963528d5df80af05b04b7..760ede311d1e447500f5f477820f03ef1b950a49 100644
--- a/pkg/connector/sync.go
+++ b/pkg/connector/sync.go
@@ -3,17 +3,68 @@ package connector
 import (
 	"context"
 	"fmt"
+	"time"
 
 	"github.com/rs/zerolog"
+	"go.mau.fi/util/ptr"
+	"maunium.net/go/mautrix/bridgev2"
+	"maunium.net/go/mautrix/bridgev2/simplevent"
 )
 
 func (l *LinkedInClient) syncConversations(ctx context.Context) {
 	log := zerolog.Ctx(ctx).With().Str("action", "sync_conversations").Logger()
+	log.Info().Msg("starting conversation sync")
 
-	conversations, err := l.client.GetConversations(ctx)
-	if err != nil {
-		log.Error().Err(err).Msg("failed to fetch initial inbox state")
-		return
+	lastUsedUpdatedBefore := time.Time{}
+	updatedBefore := time.Now()
+	for {
+		log := log.With().
+			Time("updated_before", updatedBefore).
+			Time("last_used_updated_before", lastUsedUpdatedBefore).
+			Logger()
+
+		if lastUsedUpdatedBefore.Equal(updatedBefore) {
+			log.Info().Msg("no more conversations found")
+			return
+		}
+		lastUsedUpdatedBefore = updatedBefore
+
+		log.Info().Msg("fetching conversations")
+
+		conversations, err := l.client.GetConversationsUpdatedBefore(ctx, updatedBefore)
+		if err != nil {
+			log.Err(err).Msg("failed to fetch conversations")
+			return
+		}
+
+		fmt.Printf("%+v\n", conversations)
+
+		for _, conv := range conversations.Elements {
+			fmt.Printf("conv=%+v\n", conv)
+			if conv.LastActivityAt.Before(updatedBefore) {
+				updatedBefore = conv.LastActivityAt.Time
+			}
+
+			meta := simplevent.EventMeta{
+				LogContext: func(c zerolog.Context) zerolog.Context {
+					return c.Str("update", "sync")
+				},
+				PortalKey:    l.makePortalKey(conv.EntityURN),
+				CreatePortal: true,
+			}
+
+			var latestMessageTS time.Time
+			for _, msg := range conv.Messages.Elements {
+				if msg.DeliveredAt.After(latestMessageTS) {
+					latestMessageTS = msg.DeliveredAt.Time
+				}
+			}
+			l.main.Bridge.QueueRemoteEvent(l.userLogin, &simplevent.ChatResync{
+				ChatInfo:            ptr.Ptr(l.conversationToChatInfo(conv)),
+				EventMeta:           meta.WithType(bridgev2.RemoteEventChatResync),
+				LatestMessageTS:     latestMessageTS,
+				BundledBackfillData: conv.Messages.Elements,
+			})
+		}
 	}
-	fmt.Printf("%+v\n", conversations)
 }
diff --git a/pkg/linkedingo/attributedtext.go b/pkg/linkedingo/attributedtext.go
index b97fe1ec3d79bcc03ccd1578fedfe7622cd33147..d413efeaefe3602cbf599a2f16854b04b83f56bf 100644
--- a/pkg/linkedingo/attributedtext.go
+++ b/pkg/linkedingo/attributedtext.go
@@ -15,11 +15,54 @@ type Attribute struct {
 }
 
 type AttributeKind struct {
-	// Entity is a user mention.
-	Entity *Entity `json:"entity,omitempty"`
+	Bold        *Bold        `json:"bold,omitempty"`
+	Entity      *Entity      `json:"entity,omitempty"` // Entity is a user mention.
+	Hyperlink   *Hyperlink   `json:"hyperlink,omitempty"`
+	Italic      *Italic      `json:"italic,omitempty"`
+	LineBreak   *LineBreak   `json:"lineBreak,omitempty"`
+	List        *List        `json:"list,omitempty"`
+	ListItem    *ListItem    `json:"listItem,omitempty"`
+	Paragraph   *Paragraph   `json:"paragraph,omitempty"`
+	Subscript   *Subscript   `json:"subscript,omitempty"`
+	Superscript *Superscript `json:"superscript,omitempty"`
+	Underline   *Underline   `json:"underline,omitempty"`
 }
 
+// Bold represents a com.linkedin.pemberly.text.Bold object.
+type Bold struct{}
+
 // Entity represents a com.linkedin.pemberly.text.Entity object.
 type Entity struct {
 	URN URN `json:"urn,omitempty"`
 }
+
+// Hyperlink represents a com.linkedin.pemberly.text.Hyperlink object.
+type Hyperlink struct {
+	URL string `json:"url,omitempty"`
+}
+
+// Italic represents a com.linkedin.pemberly.text.Italic object.
+type Italic struct{}
+
+// LineBreak represents a com.linkedin.pemberly.text.LineBreak object.
+type LineBreak struct{}
+
+// List represents a com.linkedin.pemberly.text.List object.
+type List struct {
+	Ordered bool `json:"ordered,omitempty"`
+}
+
+// ListItem represents a com.linkedin.pemberly.text.ListItem object.
+type ListItem struct{}
+
+// Paragraph represents a com.linkedin.pemberly.text.Paragraph object.
+type Paragraph struct{}
+
+// Subscript represents a com.linkedin.pemberly.text.Subscript object.
+type Subscript struct{}
+
+// Superscript represents a com.linkedin.pemberly.text.Superscript object.
+type Superscript struct{}
+
+// Underline represents a com.linkedin.pemberly.text.Underline object.
+type Underline struct{}
diff --git a/pkg/linkedingo/conversations.go b/pkg/linkedingo/conversations.go
index d6144b6df2e6d9103bf97ff194d1a9a2f4aceb69..c674423f9eb0d75c372ad15c699ac6c425a3d21d 100644
--- a/pkg/linkedingo/conversations.go
+++ b/pkg/linkedingo/conversations.go
@@ -4,51 +4,43 @@ import (
 	"context"
 	"encoding/json"
 	"net/http"
-)
-
-// https://www.linkedin.com/voyager/api/voyagerMessagingGraphQL/graphql?queryId=messengerConversations.7b27164c5517548167d9adb4ba603e55&variables=(mailboxUrn:urn%3Ali%3Afsd_profile%3AACoAADZsHU0BD7Cr7MwzvkzsAcCoeOii7kl0mPU)
-
-// https://www.linkedin.com/voyager/api/voyagerMessagingGraphQL/graphql?queryId=messengerConversations.8656fb361a8ad0c178e8d3ff1a84ce26&variables=(query:(predicateUnions:List((conversationCategoryPredicate:(category:PRIMARY_INBOX)))),count:20,mailboxUrn:urn%3Ali%3Afsd_profile%3AACoAADZsHU0BD7Cr7MwzvkzsAcCoeOii7kl0mPU,lastUpdatedBefore:1739209141023)
-
-// curl 'https://www.linkedin.com/voyager/api/voyagerMessagingGraphQL/graphql?queryId=messengerConversations.277103fa0741e804ec5f21e6f64cb598&variables=(mailboxUrn:urn%3Ali%3Afsd_profile%3AACoAADZsHU0BD7Cr7MwzvkzsAcCoeOii7kl0mPU,syncToken:-trA4KJljszB4KJlLnVybjpsaTpmYWJyaWM6cHJvZC1sb3IxAA%3D%3D)' \
+	"net/url"
+	"strconv"
+	"time"
 
-// type GetThreadsVariables struct {
-// 	InboxCategory     InboxCategory `graphql:"category"`
-// 	Count             int64         `graphql:"count"`
-// 	MailboxUrn        string        `graphql:"mailboxUrn"`
-// 	LastUpdatedBefore int64         `graphql:"lastUpdatedBefore"`
-// 	NextCursor        string        `graphql:"nextCursor"`
-// 	SyncToken         string        `graphql:"syncToken"`
-// }
+	"github.com/rs/zerolog"
+	"go.mau.fi/util/jsontime"
+)
 
-type GraphQlResponse[T any] struct {
-	Data GraphQLData[T] `json:"data,omitempty"`
+type GraphQlResponse struct {
+	Data GraphQLData `json:"data,omitempty"`
 }
 
-type GraphQLData[T any] struct {
-	MessengerConversationsBySyncToken *CollectionResponse[T] `json:"messengerConversationsBySyncToken,omitempty"`
+type GraphQLData struct {
+	MessengerConversationsByCategoryQuery *CollectionResponse[ConversationCursorMetadata, Conversation] `json:"messengerConversationsByCategoryQuery,omitempty"`
 }
 
 // CollectionResponse represents a
 // com.linkedin.restli.common.CollectionResponse object.
-type CollectionResponse[T any] struct {
-	Metadata SyncMetadata `json:"metadata,omitempty"`
-	Elements []T          `json:"elements,omitempty"`
+type CollectionResponse[M, T any] struct {
+	Metadata M   `json:"metadata,omitempty"`
+	Elements []T `json:"elements,omitempty"`
 }
 
-// SyncMetadata represents a com.linkedin.messenger.SyncMetadata object.
-type SyncMetadata struct {
-	NewSyncToken string `json:"newSyncToken,omitempty"`
+// ConversationCursorMetadata represents a com.linkedin.messenger.ConversationCursorMetadata object.
+type ConversationCursorMetadata struct {
+	NextCursor string `json:"nextCursor,omitempty"`
 }
 
 // Conversation represents a com.linkedin.messenger.Conversation object
 type Conversation struct {
-	Title                    string                 `json:"title,omitempty"`
-	EntityURN                URN                    `json:"entityUrn,omitempty"`
-	GroupChat                bool                   `json:"groupChat,omitempty"`
-	ConversationParticipants []MessagingParticipant `json:"conversationParticipants,omitempty"`
-	Read                     bool                   `json:"read,omitempty"`
-	// Messages                 []CollectionResponse[Message] `json:"messages,omitempty"`
+	Title                    string                           `json:"title,omitempty"`
+	EntityURN                URN                              `json:"entityUrn,omitempty"`
+	LastActivityAt           jsontime.UnixMilli               `json:"lastActivityAt,omitempty"`
+	GroupChat                bool                             `json:"groupChat,omitempty"`
+	ConversationParticipants []MessagingParticipant           `json:"conversationParticipants,omitempty"`
+	Read                     bool                             `json:"read,omitempty"`
+	Messages                 CollectionResponse[any, Message] `json:"messages,omitempty"`
 }
 
 // MessagingParticipant represents a
@@ -59,7 +51,8 @@ type MessagingParticipant struct {
 }
 
 type ParticipantType struct {
-	Member MemberParticipantInfo `json:"member,omitempty"`
+	Member       *MemberParticipantInfo       `json:"member,omitempty"`
+	Organization *OrganizationParticipantInfo `json:"organization,omitempty"`
 }
 
 // MemberParticipantInfo represents a
@@ -73,22 +66,25 @@ type MemberParticipantInfo struct {
 	Headline       AttributedText `json:"headline,omitempty"`
 }
 
-func (c *Client) GetConversations(ctx context.Context) (*CollectionResponse[Conversation], error) {
-	variables := map[string]string{
-		"mailboxUrn": c.userEntityURN.WithPrefix("urn", "li", "fsd_profile").String(),
-	}
-
-	// withCursor := variables.LastUpdatedBefore != 0 && variables.NextCursor != ""
-
-	queryId := graphQLQueryIDMessengerConversations
-	// if withCursor {
-	// 	queryId = graphQLQueryIDMessengerConversationsWithCursor
-	// } else if variables.SyncToken != "" {
-	// 	queryId = graphQLQueryIDMessengerConversationsWithSyncToken
-	// }
+// OrganizationParticipantInfo represents a
+// com.linkedin.messenger.OrganizationParticipantInfo object.
+type OrganizationParticipantInfo struct {
+	Name    AttributedText `json:"name,omitempty"`
+	Logo    *VectorImage   `json:"logo,omitempty"`
+	PageURL string         `json:"pageUrl,omitempty"`
+}
 
+func (c *Client) GetConversationsUpdatedBefore(ctx context.Context, updatedBefore time.Time) (*CollectionResponse[ConversationCursorMetadata, Conversation], error) {
+	zerolog.Ctx(ctx).Info().
+		Time("updated_before", updatedBefore).
+		Msg("Getting conversations updated before")
 	resp, err := c.newAuthedRequest(http.MethodGet, linkedInVoyagerMessagingGraphQLURL).
-		WithGraphQLQuery(queryId, variables).
+		WithGraphQLQuery(graphQLQueryIDMessengerConversationsWithCursor, map[string]string{
+			"mailboxUrn":        url.QueryEscape(c.userEntityURN.WithPrefix("urn", "li", "fsd_profile").String()),
+			"lastUpdatedBefore": strconv.Itoa(int(updatedBefore.UnixMilli())),
+			"count":             "20",
+			"query":             "(predicateUnions:List((conversationCategoryPredicate:(category:PRIMARY_INBOX))))",
+		}).
 		WithCSRF().
 		WithXLIHeaders().
 		WithHeader("accept", contentTypeGraphQL).
@@ -97,55 +93,6 @@ func (c *Client) GetConversations(ctx context.Context) (*CollectionResponse[Conv
 		return nil, err
 	}
 
-	var response GraphQlResponse[Conversation]
-	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
-		return nil, err
-	}
-
-	return response.Data.MessengerConversationsBySyncToken, nil
+	var response GraphQlResponse
+	return response.Data.MessengerConversationsByCategoryQuery, json.NewDecoder(resp.Body).Decode(&response)
 }
-
-// func (c *Client) GetThreads(variables queryold.GetThreadsVariables) (*responseold.MessengerConversationsResponse, error) {
-// 	if variables.MailboxUrn == "" {
-// 		variables.MailboxUrn = c.PageLoader.CurrentUser.FsdProfileID
-// 	}
-//
-// 	withCursor := variables.LastUpdatedBefore != 0 && variables.NextCursor != ""
-// 	var queryId typesold.GraphQLQueryIDs
-// 	if withCursor {
-// 		queryId = typesold.GraphQLQueryIDMessengerConversationsWithCursor
-// 	} else if variables.SyncToken != "" {
-// 		queryId = typesold.GraphQLQueryIDMessengerConversationsWithSyncToken
-// 	} else {
-// 		queryId = typesold.GraphQLQueryIDMessengerConversations
-// 	}
-//
-// 	variablesQuery, err := variables.Encode()
-// 	if err != nil {
-// 		return nil, err
-// 	}
-//
-// 	threadQuery := queryold.GraphQLQuery{
-// 		QueryID:   queryId,
-// 		Variables: string(variablesQuery),
-// 	}
-//
-// 	_, respData, err := c.MakeRoutingRequest(routingold.LinkedInVoyagerMessagingGraphQLURL, nil, &threadQuery)
-// 	if err != nil {
-// 		return nil, err
-// 	}
-// 	fmt.Printf("%s\n", respData)
-//
-// 	graphQLResponse, ok := respData.(*responseold.GraphQlResponse)
-// 	if !ok || graphQLResponse == nil {
-// 		return nil, newErrorResponseTypeAssertFailed("*responseold.GraphQlResponse")
-// 	}
-//
-// 	graphQLResponseData := graphQLResponse.Data
-// 	fmt.Printf("%+v\n", graphQLResponseData)
-// 	if withCursor {
-// 		return graphQLResponseData.MessengerConversationsByCategory, nil
-// 	}
-//
-// 	return graphQLResponseData.MessengerConversationsBySyncToken, nil
-// }
diff --git a/pkg/linkedingo/messages.go b/pkg/linkedingo/messages.go
index a78d7c6ab1748acfc651cfb5c8b1e99df5f204b8..bd412225a18e565b5a015490d0822a23abe869cc 100644
--- a/pkg/linkedingo/messages.go
+++ b/pkg/linkedingo/messages.go
@@ -190,7 +190,7 @@ func (c *Client) RecallMessage(ctx context.Context, messageURN URN) error {
 	return nil
 }
 
-func (c *Client) FetchMessages(ctx context.Context, variables queryold.FetchMessagesVariables) (*responseold.MessengerMessagesResponse, error) {
+func (c *Client) FetchMessages(ctx context.Context, conversationURN URN, variables queryold.FetchMessagesVariables) (*responseold.MessengerMessagesResponse, error) {
 	withCursor := variables.PrevCursor != ""
 	withAnchorTimestamp := !variables.DeliveredAt.IsZero()
 
diff --git a/pkg/linkedingo/request.go b/pkg/linkedingo/request.go
index 968083c7ac1abc01ba8cfc455e09cdcc7d21ea65..c826823ac0d7844cd0457c0b5e055a9c0a63a6ea 100644
--- a/pkg/linkedingo/request.go
+++ b/pkg/linkedingo/request.go
@@ -85,7 +85,7 @@ func (a *authedRequest) WithGraphQLQuery(queryID string, variables map[string]st
 		first = false
 		queryStr.WriteString(k)
 		queryStr.WriteByte(':')
-		queryStr.WriteString(url.QueryEscape(v))
+		queryStr.WriteString(v)
 	}
 	queryStr.WriteString(")")
 	a.rawQuery = queryStr.String()