SavvySolve Docs

Google Meet Integration

Video conferencing escalation for employed solvers using Google Workspace

Google Meet Integration

SavvySolve supports Google Meet as a video conferencing option for employed solvers (W-2 employees). When chat and screen sharing aren't sufficient to resolve a customer's issue, employed solvers can escalate to a face-to-face video call with a single click. The system automatically generates a unique Meet link and sends it to the customer via SMS.

This integration addresses a core insight from the PRD: some technical problems require visual demonstration or screen sharing that goes beyond what our built-in tools can provide. Google Meet offers enterprise-grade video quality and reliability for these complex support scenarios.

Google Meet is only available to employed solvers (W-2) who have a Google Workspace email configured. Contract solvers (1099) use the built-in screen sharing functionality.

Solver Employment Types

SavvySolve distinguishes between two types of solvers based on their employment relationship:

lib/db/schema/enums.ts
export const solverEmploymentEnum = pgEnum("solver_employment", [
  "employed",  // W-2 employees with Google Workspace access
  "contract",  // 1099 contractors using built-in tools only
]);

This distinction drives feature availability. Employed solvers have access to company Google Workspace accounts, which enables the Calendar API integration needed to generate Meet links programmatically. Contract solvers, operating as independent contractors, use the platform's native screen sharing capabilities instead.

The employment type is stored on the solver profile alongside their Google email:

lib/db/schema/solvers.ts
export const solvers = pgTable("solvers", {
  // ...other fields
  employment: solverEmploymentEnum("employment")
    .notNull()
    .default("contract"),
  googleEmail: varchar("google_email", { length: 255 }),
});

The Meet integration uses Google Calendar API to create calendar events with attached conference data. When a solver requests a Meet link, the system creates a temporary calendar event on the solver's Google Workspace calendar, and Google automatically provisions a Meet conference room.

lib/google/meet.ts
export async function generateMeetLink(
  solverEmail: string,
  customerName: string,
  sessionId: string
): Promise<MeetLinkResult> {
  const calendar = getCalendarClient();

  const event = await calendar.events.insert({
    calendarId: solverEmail,
    conferenceDataVersion: 1,
    requestBody: {
      summary: `SavvySolve Support Session - ${customerName}`,
      description: `Support session ID: ${sessionId}`,
      conferenceData: {
        createRequest: {
          requestId: `savvysolve-${sessionId}-${Date.now()}`,
          conferenceSolutionKey: {
            type: "hangoutsMeet",
          },
        },
      },
    },
  });

  const meetLink = event.data.conferenceData?.entryPoints?.find(
    (ep) => ep.entryPointType === "video"
  )?.uri;

  return { success: true, meetLink };
}

The service account authenticates via a base64-encoded JSON key stored in the GOOGLE_SERVICE_ACCOUNT_KEY environment variable. This approach avoids storing sensitive credentials in files while allowing deployment across different environments.

Fallback Strategy

If the Calendar API fails for any reason (service outage, quota limits, misconfiguration), the system falls back to https://meet.google.com/new. This URL allows the solver to manually create a meeting room, ensuring video calls remain possible even during API issues.

lib/google/meet.ts
export function getFallbackMeetLink(): string {
  return "https://meet.google.com/new";
}

tRPC API Reference

The Meet router provides two procedures for the frontend to interact with:

meet.canUseMeet

Checks whether the current solver is eligible to use Google Meet. This query runs on component mount to determine whether to render the Meet button.

server/routers/meet.ts
canUseMeet: protectedProcedure.query(async ({ ctx }) => {
  const { solver } = await getSolverForUser(ctx.auth.userId);

  if (solver.employment !== "employed") {
    return { canUse: false, reason: "Google Meet is only available for employed solvers" };
  }

  if (!solver.googleEmail) {
    return { canUse: false, reason: "Google Workspace email not configured" };
  }

  if (!isMeetConfigured()) {
    return { canUse: false, reason: "Google Meet integration not configured" };
  }

  return { canUse: true };
});

Creates a new Meet link for a session, stores it in the database, broadcasts a system message via Ably, and sends the link to the customer via SMS.

Input:

  • sessionId (string, UUID) - The session to create a Meet link for

Response:

  • success (boolean) - Whether the operation completed
  • meetLink (string) - The generated or fallback Meet URL
  • isExisting (boolean) - True if returning a previously created link
  • usedFallback (boolean) - True if the fallback URL was used

The procedure performs several validations:

  1. Verifies the solver is employed (W-2)
  2. Confirms Google email is configured
  3. Checks session exists and belongs to this solver
  4. Returns existing link if session already has one

Database Schema

Sessions store the Meet link once created:

lib/db/schema/sessions.ts
export const sessions = pgTable("sessions", {
  // ...other fields
  meetLink: text("meet_link"),
});

This allows the link to persist across page refreshes and enables the customer session view to display the Meet link if one exists.

Frontend Component

The MeetButton component handles the UI interaction. It only renders for employed solvers with proper configuration:

components/session/MeetButton.tsx
export function MeetButton({ sessionId, existingMeetLink }: MeetButtonProps) {
  const { data: canUseMeet, isLoading } = trpc.meet.canUseMeet.useQuery();
  const createMeetLink = trpc.meet.createMeetLink.useMutation({
    onSuccess: (data) => {
      if (data.meetLink) {
        setMeetLink(data.meetLink);
        window.open(data.meetLink, "_blank", "noopener,noreferrer");
      }
    },
  });

  // Only render if solver can use Meet
  if (!canUseMeet?.canUse) return null;

  // Button changes from "Start Google Meet" to "Open Meet" after creation
}

The button appears in the session header alongside other session controls. When clicked for the first time, it:

  1. Calls the createMeetLink mutation
  2. Opens the Meet link in a new tab
  3. The customer receives an SMS with the join link

Subsequent clicks simply reopen the existing link.

SMS Notification

When a Meet link is created, the customer receives an SMS notification:

lib/telnyx/sms.ts
export async function sendMeetLink(
  to: string,
  sessionId: string,
  meetLink: string,
  solverName: string
): Promise<SendSmsResult> {
  const body = `${solverName} has started a video call for your support session. Join here: ${meetLink}`;
  return sendSms(to, body, sessionId);
}

This ensures customers without the session view open still receive the video call invitation promptly.

Configuration

Environment Variables

VariableDescription
GOOGLE_SERVICE_ACCOUNT_KEYBase64-encoded service account JSON credentials

Google Cloud Setup

  1. Create a Google Cloud project
  2. Enable the Google Calendar API
  3. Create a service account with domain-wide delegation
  4. Download the JSON key and base64-encode it
  5. Grant the service account calendar access to solver email accounts

The service account needs domain-wide delegation configured in Google Workspace Admin Console to create events on behalf of users.

Testing Strategy

The Meet integration is tested at multiple levels:

Unit tests (lib/google/meet.test.ts) verify:

  • Meet link generation returns expected format
  • Fallback link is returned on API failure
  • Configuration detection works correctly

Router tests (server/routers/meet.test.ts) verify:

  • Authorization checks for employment type
  • Session ownership validation
  • Existing link returns without regeneration
  • Fallback behavior when API fails

The tests mock the Google Calendar API using Vitest's mocking capabilities to avoid making real API calls during CI.

On this page