diff --git a/apps/web/playwright/team/expects.ts b/apps/web/playwright/team/expects.ts
new file mode 100644
index 00000000000000..43e02063f65f08
--- /dev/null
+++ b/apps/web/playwright/team/expects.ts
@@ -0,0 +1,29 @@
+import type { Page } from "@playwright/test";
+import { expect } from "@playwright/test";
+import { JSDOM } from "jsdom";
+import type { API, Messages } from "mailhog";
+
+import { getEmailsReceivedByUser } from "../lib/testUtils";
+
+export async function expectInvitationEmailToBeReceived(
+ page: Page,
+ emails: API | undefined,
+ userEmail: string,
+ subject: string,
+ returnLink?: string
+) {
+ if (!emails) return null;
+
+ // eslint-disable-next-line playwright/no-wait-for-timeout
+ await page.waitForTimeout(10000);
+ const receivedEmails = await getEmailsReceivedByUser({ emails, userEmail });
+ expect(receivedEmails?.total).toBe(1);
+
+ const [firstReceivedEmail] = (receivedEmails as Messages).items;
+
+ expect(firstReceivedEmail.subject).toBe(subject);
+ if (!returnLink) return;
+ const dom = new JSDOM(firstReceivedEmail.html);
+ const anchor = dom.window.document.querySelector(`a[href*="${returnLink}"]`);
+ return anchor?.getAttribute("href");
+}
diff --git a/apps/web/playwright/team/team-invitation.e2e.ts b/apps/web/playwright/team/team-invitation.e2e.ts
new file mode 100644
index 00000000000000..95505bf279656b
--- /dev/null
+++ b/apps/web/playwright/team/team-invitation.e2e.ts
@@ -0,0 +1,124 @@
+import { expect } from "@playwright/test";
+
+import { WEBAPP_URL } from "@calcom/lib/constants";
+
+import { test } from "../lib/fixtures";
+import { localize } from "../lib/testUtils";
+import { expectInvitationEmailToBeReceived } from "./expects";
+
+test.describe.configure({ mode: "parallel" });
+
+test.afterEach(async ({ users, emails, clipboard }) => {
+ clipboard.reset();
+ await users.deleteAll();
+ emails?.deleteAll();
+});
+
+test.describe("Team", () => {
+ test("Invitation (non verified)", async ({ browser, page, users, emails, clipboard }) => {
+ const t = await localize("en");
+ const teamOwner = await users.create(undefined, { hasTeam: true });
+ const { team } = await teamOwner.getFirstTeam();
+ await teamOwner.apiLogin();
+ await page.goto(`/settings/teams/${team.id}/members`);
+ await page.waitForLoadState("networkidle");
+
+ await test.step("To the team by email (external user)", async () => {
+ const invitedUserEmail = `rick_${Date.now()}@domain-${Date.now()}.com`;
+ await page.locator(`button:text("${t("add")}")`).click();
+ await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
+ await page.locator(`button:text("${t("send_invite")}")`).click();
+ await page.waitForLoadState("networkidle");
+ const inviteLink = await expectInvitationEmailToBeReceived(
+ page,
+ emails,
+ invitedUserEmail,
+ `${team.name}'s admin invited you to join the team ${team.name} on Cal.com`,
+ "signup?token"
+ );
+
+ //Check newly invited member exists and is pending
+ await expect(
+ page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
+ ).toHaveCount(1);
+
+ // eslint-disable-next-line playwright/no-conditional-in-test
+ if (!inviteLink) return null;
+
+ // Follow invite link to new window
+ const context = await browser.newContext();
+ const newPage = await context.newPage();
+ await newPage.goto(inviteLink);
+ await newPage.waitForLoadState("networkidle");
+
+ // Check required fields
+ await newPage.locator("button[type=submit]").click();
+ await expect(newPage.locator('[data-testid="hint-error"]')).toHaveCount(3);
+ await newPage.locator("input[name=password]").fill(`P4ssw0rd!`);
+ await newPage.locator("button[type=submit]").click();
+ await newPage.waitForURL("/getting-started?from=signup");
+ await newPage.close();
+ await context.close();
+
+ // Check newly invited member is not pending anymore
+ await page.bringToFront();
+ await page.goto(`/settings/teams/${team.id}/members`);
+ await page.waitForLoadState("networkidle");
+ await expect(
+ page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
+ ).toHaveCount(0);
+ });
+
+ await test.step("To the team by invite link", async () => {
+ const user = await users.create({
+ email: `user-invite-${Date.now()}@domain.com`,
+ password: "P4ssw0rd!",
+ });
+ await page.locator(`button:text("${t("add")}")`).click();
+ await page.locator(`[data-testid="copy-invite-link-button"]`).click();
+ const inviteLink = await clipboard.get();
+ await page.waitForLoadState("networkidle");
+
+ const context = await browser.newContext();
+ const inviteLinkPage = await context.newPage();
+ await inviteLinkPage.goto(inviteLink);
+ await inviteLinkPage.waitForLoadState("domcontentloaded");
+
+ await inviteLinkPage.locator("button[type=submit]").click();
+ await expect(inviteLinkPage.locator('[data-testid="field-error"]')).toHaveCount(2);
+
+ await inviteLinkPage.locator("input[name=email]").fill(user.email);
+ await inviteLinkPage.locator("input[name=password]").fill(user.username || "P4ssw0rd!");
+ await inviteLinkPage.locator("button[type=submit]").click();
+
+ await inviteLinkPage.waitForURL(`${WEBAPP_URL}/teams**`);
+ });
+ });
+
+ test("Invitation (verified)", async ({ browser, page, users, emails }) => {
+ const t = await localize("en");
+ const teamOwner = await users.create({ name: `team-owner-${Date.now()}` }, { hasTeam: true });
+ const { team } = await teamOwner.getFirstTeam();
+ await teamOwner.apiLogin();
+ await page.goto(`/settings/teams/${team.id}/members`);
+ await page.waitForLoadState("networkidle");
+
+ await test.step("To the organization by email (internal user)", async () => {
+ const invitedUserEmail = `rick@example.com`;
+ await page.locator(`button:text("${t("add")}")`).click();
+ await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
+ await page.locator(`button:text("${t("send_invite")}")`).click();
+ await page.waitForLoadState("networkidle");
+ await expectInvitationEmailToBeReceived(
+ page,
+ emails,
+ invitedUserEmail,
+ `${teamOwner.name} invited you to join the team ${team.name} on Cal.com`
+ );
+
+ await expect(
+ page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
+ ).toHaveCount(1);
+ });
+ });
+});
diff --git a/packages/features/ee/teams/components/MemberListItem.tsx b/packages/features/ee/teams/components/MemberListItem.tsx
index 2b356747caeb5a..2f2bacfa32e625 100644
--- a/packages/features/ee/teams/components/MemberListItem.tsx
+++ b/packages/features/ee/teams/components/MemberListItem.tsx
@@ -152,7 +152,14 @@ export default function MemberListItem(props: Props) {
{props.member.role &&