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.
Link Format
Standard Private Route Link
await shortLink(`${origin}/sign-in?path=/view/reservation/${orderNumber}&companyId=${companyId.slice(0, companyId.length - 8)}`)Components Breakdown
shortLink()- Always wrap URLs in this function to create shortened links${origin}- Application domain (e.g.,https://cloud.toprent.app)/sign-in- Authentication entry point?path=- Query parameter for post-login redirect/view/reservation/${orderNumber}- Target private route&companyId=- Company context for proper access control${companyId.slice(0, companyId.length - 8)}- Shortened company ID (removes last 8 characters)
How Company Redirect Works
- User clicks email link → Redirected to shortened URL
- Shortened URL expands → User lands on
/sign-in?path=/view/reservation/12345&companyId=abc123(partial company ID) - Authentication check → If not logged in, show sign-in form
- Company matching → System finds the full company ID by matching the partial ID against user’s available companies
- Post-login redirect → After successful authentication, user is redirected to the
pathparameter with proper company context - Company context loaded → Application switches to the matched company and shows the requested page
Usage Examples
Order/Reservation Links
const orderLink = await shortLink(`${origin}/sign-in?path=/view/reservation/${orderNumber}&companyId=${companyId.slice(0, companyId.length - 8)}`);Vehicle Links
const vehicleLink = await shortLink(`${origin}/sign-in?path=/view/vehicle/${vehicleId}&companyId=${companyId.slice(0, companyId.length - 8)}`);Settings Links
const settingsLink = await shortLink(`${origin}/sign-in?path=${encodeURIComponent('/settings?tab=INTEGRATIONS')}&companyId=${companyId.slice(0, companyId.length - 8)}`);Billion Order Links
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
companyIdparameter 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 shortLinkimport { shortLink } from '@power-rent/lib/shortener';
// 2. Build the URL with proper parametersconst 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 linkconst shortenedUrl = await shortLink(privateRouteUrl);
// 4. Use in email templateconst emailOptions = { variables: { linkToApp: shortenedUrl, // ... other variables }};Link Shortening Service
How shortLink() Works
The shortLink() function creates a shortened URL that:
- Reduces email length and improves deliverability
- Masks internal URL structure
// Input: Long URLconst longUrl = `https://cloud.toprent.app/sign-in?path=/view/reservation/ORD-123456&companyId=comp_abc123`;
// Output: Shortened URLconst shortUrl = await shortLink(longUrl);// Result: https://short.toprent.app/xyz789Authentication Flow
Step-by-Step Process
-
Email Link Generation
const emailLink = await shortLink(`${origin}/sign-in?path=/view/reservation/${orderNumber}&companyId=${shortCompanyId}`); -
User Clicks Link
- Shortened URL redirects to full URL
- Browser navigates to
/sign-inpage
-
Authentication Check
// App checks if user is authenticatedif (!isAuthenticated) {// Show sign-in form// Preserve redirect parameters} -
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 IDconst company = userCompanies.find((c) => c.companyId.startsWith(partialCompanyId));// Switch company contextawait switchToCompany(company.companyId);// Navigate to intended pagerouter.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 encodingconst settingsPath = encodeURIComponent('/settings?tab=BILLING§ion=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 handlingconst 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 }};Testing Links
Development Testing
// Test link generation in developmentconst 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 contextThis documentation ensures all team members understand how to properly generate secure, trackable, and user-friendly links in email communications.