Skip to content

feat(linkedin): publish @[Name](urn) mentions as LinkedIn mentions - #15

Merged
antoniolg merged 3 commits into
antoniolg:mainfrom
maximofn:feat/linkedin-mentions
Nov 11, 2026
Merged

antoniolg merged 3 commits into
antoniolg:mainfrom
maximofn:feat/linkedin-mentions

Conversation

@maximofn

Copy link
Copy Markdown
Contributor

Problem

There is no way to mention a member or a company page in a LinkedIn post published through PostFlow.

  • Root posts with media (and posts without a link) go through /v2/ugcPosts. That API does not parse any mention syntax in shareCommentary.text; mentions have to be sent as attributes with a start/length range. PostFlow only sends text.
  • Article posts go through /rest/posts, whose commentary is little text and does support @[Name](urn), but escapeLinkedInLittleText escapes it (the test mention syntax stays literal pinned that).

Real check: a post sent through PostFlow to a company page with @[Welcome to La Secta](urn:li:organization:110315077) rendered those characters literally instead of linking the page.

Fix

Use the little text mention syntax as the single way to write a mention in post text, and translate it per LinkedIn API:

  • @[Name](urn:li:person:ID) and @[Name](urn:li:organization:ID) are recognised (linkedInMentionPattern). Any other URN type stays literal.
  • ugcPosts and comments (linkedInAttributedText): the mention is replaced by Name and an attribute links that range to MemberAttributedEntity / CompanyAttributedEntity. Offsets are counted in UTF-16 code units, so emoji and the Unicode bold/italic produced by formatPostTextForPublish before the mention don't shift the range. The media title uses the same rendered text, so the raw syntax never leaks there.
  • Article posts: escapeLinkedInLittleText keeps each mention as a MentionElement and escapes only its display name; everything else is escaped exactly as before.

Because the mention lives in the text, nothing changes in the schema, the API contract, MCP or the CLI: every surface gets it for free. README.md and the postflow-cli skill document the syntax and LinkedIn's name-matching rule (the name must match the member's first, last or full name, or the page's full name, case sensitive).

Behavior change: text that already contained @[Name](urn:li:person|organization:ID) used to be published literally and now becomes a mention. The mention syntax stays literal case is replaced by mention stays a mention.

Testing

  • gofmt -l is clean, go vet, go test ./internal/postflow/ and go test -race ./internal/postflow/ pass locally (Go 1.27.4).
  • New tests: TestLinkedInMentionsToAttributes (UTF-16 offsets, person + organization, other URN types untouched) and TestLinkedInPlainTextPathsSendMentionsAsAttributes (ugcPosts and comment payloads end to end through Publish, with an ID containing underscores to check the Markdown formatter leaves it alone). Little text cases cover mentions mixed with reserved characters.
  • Not run locally: the rest of go test ./... (db, worker, api, cli, parity). My sandbox could not download modernc.org/sqlite and golang.org/x/crypto, so please rely on CI for those. golangci-lint was not run either.

Not verified against LinkedIn yet

I'll check these with a real post once this is deployed:

  • That LinkedIn counts start/length in UTF-16 units (it's a Java API, and its docs only say "character index").
  • The UGC docs say start + length must be less than the length of the text. If that is literal, a mention that ends the text would be rejected with a 400.
  • Mentions in comments (message.attributes) use the same AttributedText shape as shareCommentary.

Out of scope

  • Looking up a member's person URN. LinkedIn's People Typeahead API is restricted to approved apps, so callers have to supply the URN.

https://claude.ai/code/session_01WQdhzab1mHnyisvobQRoXF

maximofn and others added 3 commits November 11, 2026 06:27
Text written as @[Name](urn:li:person:ID) or @[Name](urn:li:organization:ID)
now becomes a real mention on every LinkedIn path:

- ugcPosts and comments: the name is sent as plain text plus an attribute
  (start/length in UTF-16 units) pointing to the member or organization.
- Article posts (little text): the mention is kept as a MentionElement and
  only its display name is escaped; everything else is still escaped.

Claude-Session: https://claude.ai/code/session_01WQdhzab1mHnyisvobQRoXF
@antoniolg
antoniolg merged commit 373e398 into antoniolg:main Nov 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants