PSA/docs/email/email-i18n-implementation-summary.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

9.9 KiB
Raw Permalink Blame History

Email Internationalization (i18n) Implementation Summary

Completed: Multi-Language Email Support

All email notifications in Alga PSA now support multiple languages with automatic language detection based on user preferences.


What Was Implemented

1. Language Resolution System

File: packages/notifications/src/notifications/emailLocaleResolver.ts

Hierarchical language detection:

  1. User preference (from user_preferences table)
  2. Client preference (if user linked to a client)
  3. Tenant client portal default
  4. Tenant default
  5. System default (English)

2. Database Template System

Tables:

  • system_email_templates - Default templates for all tenants
  • tenant_email_templates - Tenant-specific overrides

Schema:

CREATE TABLE system_email_templates (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  language_code VARCHAR(10) NOT NULL DEFAULT 'en',
  subject TEXT NOT NULL,
  html_content TEXT,
  text_content TEXT,
  notification_subtype_id INTEGER,
  UNIQUE(name, language_code)
);

3. Smart Translation Migration

File: server/migrations/20251201090000_add_email_template_translations.cjs

  • Reads existing English templates from database
  • Translates only text content (preserves HTML/styling)
  • Supports 7 languages: en, fr, es, de, nl (full), it, pl (partial — see below)
  • Covers 12 email templates

4. Updated SystemEmailService

File: server/src/lib/email/system/SystemEmailService.ts

Added to authentication email service:

  • Language resolution via determineLocale()
  • Database template fetching via fetchTemplate()
  • Template variable replacement
  • Fallback chain (tenant → system → hardcoded)
  • Emergency fallbacks for reliability

Breaking change: Method signatures now accept optional parameters:

// Before
await service.sendEmailVerification(data);

// After (backward compatible)
await service.sendEmailVerification(data, {
  tenantId: 'uuid',
  userId: 'uuid',
  locale: 'fr'  // optional override
});

5. Updated Event Email System

Files:

  • server/src/lib/notifications/sendEventEmail.ts
  • server/src/lib/notifications/email.ts

Added language support to ticket/invoice/payment notifications.


Supported Templates

Authentication (12 templates × 5 languages = 60 total):

  1. email-verification - Email address verification
  2. password-reset - Password reset request
  3. portal-invitation - Portal access invitation
  4. ticket-created - New ticket notification
  5. ticket-assigned - Ticket assignment
  6. ticket-updated - Ticket update
  7. ticket-closed - Ticket closure
  8. ticket-comment-added - New comment
  9. invoice-generated - New invoice
  10. payment-received - Payment confirmation
  11. payment-overdue - Overdue notice
  12. credits-expiring - Credits expiring soon

Languages (client-facing email templates):

  • English (en) - default
  • French (fr)
  • Spanish (es)
  • German (de)
  • Dutch (nl)
  • ⚠️ Italian (it) — partial (18/36 templates; missing: projects, SLA, time entry, billing misc)
  • ⚠️ Polish (pl) — partial (17/36 templates; missing: emailVerification, projects, SLA, time entry, billing misc)

Note: Email locale resolution currently serves client portal users only. MSP internal users always receive English emails regardless of their language preference.


Files Changed

New Files:

  • server/src/lib/notifications/emailLocaleResolver.ts
  • server/migrations/20251027120000_add_system_auth_email_templates.cjs
  • server/migrations/20251201090000_add_email_template_translations.cjs
  • server/seeds/dev/86_add_styled_email_template_translations.cjs
  • docs/email-language-architecture-complete.md
  • docs/email-i18n-implementation-summary.md

Modified Files:

  • server/src/lib/email/system/SystemEmailService.ts - Added i18n support
  • server/src/lib/notifications/sendEventEmail.ts - Added language resolution
  • server/src/lib/notifications/email.ts - Added locale parameter

Deleted Files:

  • server/src/lib/email/system/i18nSystemEmailService.ts - Redundant (functionality moved to SystemEmailService)

Template Lookup Flow

For any email:

  1. Determine recipient's language (via emailLocaleResolver)
  2. Try tenant template (recipient's language)
  3. Try tenant template (English)
  4. Try system template (recipient's language)
  5. Try system template (English)
  6. Use hardcoded fallback (English only, logs warning)

How to Use

System Emails (Authentication):

import { getSystemEmailService } from '@/lib/email';

const service = await getSystemEmailService();

// Will automatically detect language from user/tenant settings
await service.sendEmailVerification(
  {
    email: 'user@example.com',
    verificationUrl: 'https://...',
    clientName: 'Company Name',
    expirationTime: '24 hours'
  },
  {
    tenantId: 'tenant-uuid',  // Provides context for language detection
    userId: 'user-uuid',      // Optional - helps find user preferences
    locale: 'fr'              // Optional - explicit override
  }
);

Event Emails (Notifications):

import { sendEventEmail } from '@/lib/notifications/sendEventEmail';

// Will automatically detect language
await sendEventEmail({
  tenantId: 'tenant-uuid',
  to: 'user@example.com',
  templateName: 'ticket-created',
  recipientUserId: 'user-uuid',  // Optional
  locale: 'es',                  // Optional override
  context: {
    ticket: {
      title: 'Bug Report',
      priority: 'High',
      status: 'Open'
    }
  }
});

Migration Instructions

Development:

# Run all migrations
npm run migrate

# Or seed database (includes migrations)
npm run seed

Production:

# Run migrations only
npm run migrate

Key migrations:

  1. 20251027120000_add_system_auth_email_templates.cjs - Adds auth templates
  2. 20251201090000_add_email_template_translations.cjs - Adds all translations

How to Add a New Language

  1. Update locale config:
// packages/core/src/lib/i18n/config.ts
supportedLocales: ['en', 'fr', 'es', 'de', 'nl', 'it', 'pl'] as const,
  1. Add translations to migration:
// server/migrations/20251201090000_add_email_template_translations.cjs

const translations = {
  // ... existing
  pt: {
    'New Ticket Created': 'Novo ticket criado',
    'Priority': 'Prioridade',
    // ... all phrases
  }
};

const subjectTranslations = {
  // ... existing
  pt: {
    'ticket-created': 'Novo ticket • {{ticket.title}} ({{ticket.priority}})',
    // ... all subjects
  }
};
  1. Re-run migration:
npm run migrate

Benefits

User Experience:

  • Users receive emails in their preferred language
  • Automatic language detection (no manual selection needed)
  • Consistent with client portal language preferences

Maintainability:

  • Templates stored in database (update without deployment)
  • Single source of truth for all templates
  • Smart translation preserves HTML styling automatically

Flexibility:

  • Tenants can override any template
  • Per-language customization
  • Easy to add new languages (just update migration)

Reliability:

  • Emergency fallbacks prevent email failures
  • Graceful degradation (falls back to English if needed)
  • Warnings logged for monitoring

Consistency:

  • Both email systems use same approach
  • Unified template management
  • Same language resolution logic everywhere

Testing Checklist

Language Resolution:

  • User with French preference receives French emails
  • User without preference uses tenant default language
  • Explicit locale override works
  • Falls back to English when translation missing

Template Fallbacks:

  • Tenant template overrides system template
  • English fallback works when language unavailable
  • Emergency fallback triggers when DB unavailable
  • Warning logged when emergency fallback used

Variable Replacement:

  • {{variable}} placeholders replaced correctly
  • Special characters in variables handled properly
  • Missing variables don't break email

Email Types:

  • Email verification works in all languages
  • Password reset works in all languages
  • Ticket notifications work in all languages
  • Invoice notifications work in all languages

Known Limitations

  1. Conditional logic not supported:

    • Templates use simple {{variable}} replacement
    • No {{#if}} or loops (use separate templates instead)
  2. Right-to-left languages not tested:

    • Arabic, Hebrew would need additional CSS
    • HTML structure might need adjustments
  3. Emergency fallbacks are English-only:

    • If database completely unavailable, only English works
    • This is intentional for maximum reliability
  4. Translation quality:

    • Translations are machine-generated
    • May need native speaker review for production use

Future Enhancements

Potential additions:

  • Add more languages (Portuguese, etc.)
  • Support for conditional template logic (handlebars/mustache)
  • Template versioning (track changes over time)
  • A/B testing for email content
  • Email preview in admin UI
  • Translation management UI for tenants
  • Automated translation updates via API

Conclusion

The email system now fully supports internationalization! 🎉

Key achievements:

  • 36 email templates (across auth, tickets, invoices, projects, SLA, time, surveys, appointments)
  • 5 fully translated languages (en, fr, es, de, nl), 2 partially translated (it, pl)
  • Automatic language detection for client portal users
  • Database-driven (no code changes for updates)
  • Emergency fallbacks for reliability
  • Unified architecture across both email systems

Next steps:

  1. Run migrations in production
  2. Review translations with native speakers
  3. Monitor warning logs for fallback usage
  4. Consider adding more languages based on user base