Skip to content

Email Links and Company Redirect Documentation

Overview

When sending emails with links to private application routes, all URLs must be shortened using the shortLink function and include proper company redirection parameters. The system uses partial company IDs for security and user experience optimization.

await shortLink(`${origin}/sign-in?path=/view/reservation/${orderNumber}&companyId=${companyId.slice(0, companyId.length - 8)}`)

Components Breakdown

  1. shortLink() - Always wrap URLs in this function to create shortened links
  2. ${origin} - Application domain (e.g., https://cloud.toprent.app)
  3. /sign-in - Authentication entry point
  4. ?path= - Query parameter for post-login redirect
  5. /view/reservation/${orderNumber} - Target private route
  6. &companyId= - Company context for proper access control
  7. ${companyId.slice(0, companyId.length - 8)} - Shortened company ID (removes last 8 characters)

How Company Redirect Works

  1. User clicks email link → Redirected to shortened URL
  2. Shortened URL expands → User lands on /sign-in?path=/view/reservation/12345&companyId=abc123 (partial company ID)
  3. Authentication check → If not logged in, show sign-in form
  4. Company matching → System finds the full company ID by matching the partial ID against user’s available companies
  5. Post-login redirect → After successful authentication, user is redirected to the path parameter with proper company context
  6. Company context loaded → Application switches to the matched company and shows the requested page

Usage Examples

const orderLink = await shortLink(`${origin}/sign-in?path=/view/reservation/${orderNumber}&companyId=${companyId.slice(0, companyId.length - 8)}`);
const vehicleLink = await shortLink(`${origin}/sign-in?path=/view/vehicle/${vehicleId}&companyId=${companyId.slice(0, companyId.length - 8)}`);
const settingsLink = await shortLink(`${origin}/sign-in?path=${encodeURIComponent('/settings?tab=INTEGRATIONS')}&companyId=${companyId.slice(0, companyId.length - 8)}`);
const billionLink = await shortLink(`${origin}/sign-in?path=/billion/order/${globalOrderId}&companyId=${companyId.slice(0, companyId.length - 8)}`);

Important Rules

✅ DO

  • Always use shortLink() for private routes
  • Include companyId parameter for company context
  • Use encodeURIComponent() for complex paths with special characters
  • Use shortened company ID (remove last 8 characters)

❌ DON’T

  • Send direct URLs to private routes without authentication
  • Forget to include company context
  • Use full company IDs in URLs
  • Skip link shortening for private application routes

Implementation Pattern

// 1. Import shortLink
import { shortLink } from '@power-rent/lib/shortener';
// 2. Build the URL with proper parameters
const url = new URL('/sign-in', origin);
url.searchParams.set('path', '/your/private/route');
url.searchParams.set('companyId', companyId.slice(0, companyId.length - 8));
const privateRouteUrl = url.toString();
// 3. Shorten the link
const shortenedUrl = await shortLink(privateRouteUrl);
// 4. Use in email template
const emailOptions = {
variables: {
linkToApp: shortenedUrl,
// ... other variables
}
};

The shortLink() function creates a shortened URL that:

  • Reduces email length and improves deliverability
  • Masks internal URL structure
// Input: Long URL
const longUrl = `https://cloud.toprent.app/sign-in?path=/view/reservation/ORD-123456&companyId=comp_abc123`;
// Output: Shortened URL
const shortUrl = await shortLink(longUrl);
// Result: https://short.toprent.app/xyz789

Authentication Flow

Step-by-Step Process

  1. Email Link Generation

    const emailLink = await shortLink(`${origin}/sign-in?path=/view/reservation/${orderNumber}&companyId=${shortCompanyId}`);
  2. User Clicks Link

    • Shortened URL redirects to full URL
    • Browser navigates to /sign-in page
  3. Authentication Check

    // App checks if user is authenticated
    if (!isAuthenticated) {
    // Show sign-in form
    // Preserve redirect parameters
    }
  4. Post-Authentication Redirect

    // The redirection path is validated, only allowed redirect URL of the same origin.
    const redirectPath = new URLSearchParams(window.location.search).get('path');
    const partialCompanyId = new URLSearchParams(window.location.search).get('companyId');
    // Find matching company using partial ID
    const company = userCompanies.find((c) => c.companyId.startsWith(partialCompanyId));
    // Switch company context
    await switchToCompany(company.companyId);
    // Navigate to intended page
    router.push(redirectPath);

Best Practices

1. Consistent URL Structure

Always follow the same pattern for private route URLs:

// ✅ Correct pattern
`${origin}/sign-in?path=${targetRoute}&companyId=${shortCompanyId}`
// ❌ Avoid inconsistent patterns
`${origin}${targetRoute}?company=${companyId}`

2. URL Encoding

Use proper encoding for complex paths:

// ✅ Proper encoding
const settingsPath = encodeURIComponent('/settings?tab=BILLING&section=payments');
const url = `${origin}/sign-in?path=${settingsPath}&companyId=${shortCompanyId}`;
// ❌ Without encoding (breaks URLs)
const url = `${origin}/sign-in?path=/settings?tab=BILLING&companyId=${shortCompanyId}`;

3. Async/Await Handling

Always await the shortLink function:

// ✅ Correct async handling
const emailOptions = {
variables: {
orderLink: await shortLink(orderUrl),
vehicleLink: await shortLink(vehicleUrl),
}
};
// ❌ Missing await (returns Promise object)
const emailOptions = {
variables: {
orderLink: shortLink(orderUrl), // Promise<string> instead of string
}
};

Development Testing

// Test link generation in development
const testLink = await shortLink(`${process.env.APP_DOMAIN}/sign-in?path=/test&companyId=test_comp`);
console.log('Generated link:', testLink);
// Test redirect flow
// 1. Open link in browser
// 2. Verify sign-in page loads
// 3. Complete authentication
// 4. Verify redirect to correct page with company context
This documentation ensures all team members understand how to properly generate secure, trackable, and user-friendly links in email communications.