Skip to content

Bug: Workspace invitation plain-text fallback link contains escaped query separators #9497

Description

@muemarco

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

Workspace invitation emails include a fallback text link under “If the button doesn’t work, copy and paste this link into your browser”.

In the generated plain-text email body, the URL query separators are HTML-escaped as `&` instead of plain `&`.

Example:

```text
https://example.com/workspace-invitations/?invitation_id=<id>&amp;slug=<slug>&amp;token=<token>

When this visible fallback link is copied and opened in a browser, the frontend receives query parameters like amp;slug and amp;token instead of slug and token.

As a result, the workspace invitation page stays on the loading screen and no request is made to /api/workspaces/<slug>/invitations/<invitation_id>/.

I searched existing issues for &amp;slug, workspace-invitations amp;, and copy and paste this link invitation, but did not find this exact issue.


### Steps to reproduce

1. Run Plane self-hosted with SMTP enabled.
2. Invite a user to a workspace.
3. Open the received workspace invitation email.
4. Copy the visible fallback URL from "If the button doesn't work, copy and paste this link into your browser".
5. Paste that URL into a browser.
6. The invitation page stays on the loading screen.

Expected behavior:

The copyable/plain-text fallback URL should contain plain query separators:

    https://example.com/workspace-invitations/?invitation_id=<id>&slug=<slug>&token=<token>

Opening the copied fallback link should load the invitation details and allow the invitee to accept or decline the workspace invitation.

Additional investigation:

The workspace invitation task appears to build the raw URL correctly:

    relative_link = (
        f"/workspace-invitations/?invitation_id={workspace_member_invite.id}&slug={workspace.slug}&token={token}"
    )
    abs_url = str(current_site) + relative_link

The workspace invitation email template then renders `{{abs_url}}` in HTML. HTML escaping is expected there, especially inside `href` attributes.

However, the plain-text email body is generated from the already-rendered HTML:

    html_content = render_to_string("emails/invitations/workspace_invitation.html", context)
    text_content = generate_plain_text_from_html(html_content)

The current `generate_plain_text_from_html()` implementation strips tags but does not decode HTML entities, so `&amp;` remains in the plain-text output.

This seems related to PR #8535:
https://github.com/makeplane/plane/pull/8535

Possible fix: decode HTML entities when generating the plain-text email body, for example by applying `html.unescape()` after stripping HTML tags.

### Environment

Production

### Browser

Google Chrome

### Variant

Self-hosted

### Version

v1.3.1

Metadata

Metadata

Assignees

Labels

planesync issues to Plane🐛bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions