TGM Expert includes a comprehensive email system with multi-provider support, templating, and queue management for reliable email delivery.

Table of Contents


Overview

Key Features

  • Multi-Provider Support - SendGrid, AWS SES, Mailgun, Postmark, SMTP, and more
  • Automatic Failover - Falls back to secondary providers on failure
  • Template Engine - FreeMarker-based email templates
  • Queue System - Asynchronous email processing with retries
  • Tracking - Email status tracking and delivery reports
  • Rate Limiting - Provider-specific rate limit handling
  • Multi-Tenant Support - Per-company email provider configuration

Multi-Tenancy Context

Email configuration operates within TGM's multi-tenant architecture:

┌─────────────────────────────────────────────────────────────────┐
│                    Platform Level                                │
│  System Default Email Provider (fallback)                       │
└─────────────────────────────────────────────────────────────────┘
                               │
        ┌──────────────────────┼──────────────────────┐
        ▼                      ▼                      ▼
┌───────────────┐      ┌───────────────┐      ┌───────────────┐
│   Client A    │      │   Client B    │      │   Client C    │
│   Database    │      │   Database    │      │   Database    │
├───────────────┤      ├───────────────┤      ├───────────────┤
│   Company     │      │   Company     │      │   Company     │
│  Email Config │      │  Email Config │      │  Email Config │
│  (SendGrid)   │      │  (AWS SES)    │      │  (default)    │
└───────────────┘      └───────────────┘      └───────────────┘

Key Points

  1. Email providers are per-company - Each company within a client can have its own provider
  2. Fallback to system default - If company provider fails, system default is used
  3. Provider configs stored per-client - Email provider configurations are in each client's database

API Request Headers

curl -X POST "http://localhost:1337/api/admin/email-providers" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -H "X-Client-ID: acme-corp" \
  -d '{ ... }'

Architecture

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Service   │────>│   Email     │────>│  Provider   │
│  (trigger)  │     │   Queue     │     │  (SendGrid) │
└─────────────┘     └─────────────┘     └─────────────┘
                           │                   │
                           │ On failure        │
                           ▼                   │
                    ┌─────────────┐           │
                    │  Fallback   │◀──────────┘
                    │  Provider   │
                    └─────────────┘

Email Providers

Supported Providers

Provider Type Features
SendGrid API High deliverability, analytics
AWS SES API Cost-effective at scale
Mailgun API Developer-friendly
Postmark API Transactional focused
SparkPost API Enterprise features
Mailjet API Marketing + transactional
Brevo API CRM integration
Resend API Modern API
SMTP Protocol Universal compatibility
Console Debug Development/testing only

Provider Priority

  1. Primary Provider - Configured as default for a company
  2. System Default - Fallback when company provider fails
  3. Console (Dev) - Logs emails in development mode

Configuration

Provider Configuration via Admin API

curl -X POST https://api.tgm-expert.com/api/admin/email-providers \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Company SendGrid",
    "providerType": "SENDGRID",
    "companyId": 1,
    "apiKey": "SG.xxxxxxxxxxxx",
    "fromEmail": "noreply@company.com",
    "fromName": "TGM Expert",
    "replyToEmail": "support@company.com",
    "isActive": true
  }'

Provider Types and Required Fields

SendGrid

{
  "providerType": "SENDGRID",
  "apiKey": "SG.xxxxxxxxxxxxx"
}

AWS SES

{
  "providerType": "AWS_SES",
  "awsAccessKeyId": "AKIAIOSFODNN7EXAMPLE",
  "awsSecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "awsRegion": "us-east-1"
}

Mailgun

{
  "providerType": "MAILGUN",
  "apiKey": "key-xxxxxxxxxxxxxxxx",
  "domain": "mg.company.com",
  "smtpHost": "smtp.mailgun.org"
}

SMTP (Generic)

{
  "providerType": "SMTP",
  "smtpHost": "smtp.gmail.com",
  "smtpPort": 587,
  "smtpUsername": "user@gmail.com",
  "smtpPassword": "app-password",
  "smtpStartTlsEnabled": true
}

Environment Variables

For system-wide defaults, configure via environment:

# Default email settings
app.email.default-from=noreply@tgm-expert.com
app.email.default-from-name=TGM Expert
app.email.enabled=true

# SendGrid (default provider)
spring.sendgrid.api-key=${SENDGRID_API_KEY}

# AWS SES (fallback)
aws.ses.access-key=${AWS_ACCESS_KEY}
aws.ses.secret-key=${AWS_SECRET_KEY}
aws.ses.region=us-east-1

Email Templates

Template Engine

TGM Expert uses FreeMarker for email templates. Templates are located in src/main/resources/templates/email/.

Base Layout

All emails use a consistent base layout:

<!-- base-layout.ftl -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>${subject}</title>
</head>
<body>
  <div class="container">
    <div class="header">
      <img src="${logoUrl}" alt="TGM Expert">
    </div>
    <div class="content">
      <#nested>
    </div>
    <div class="footer">
      <p>The first intelligence platform built for hydro and energy assets</p>
      <p>&copy; ${.now?string('yyyy')} TGM Expert. All rights reserved.</p>
    </div>
  </div>
</body>
</html>

Available Templates

Template Purpose Variables
welcome.ftl New user welcome user, loginUrl
password-reset.ftl Password reset user, resetUrl, expiresIn
email-verification.ftl Verify email user, verificationUrl
password-changed.ftl Password change confirmation user
login-alert.ftl New device login user, device, location, time
two-factor-code.ftl 2FA code user, code, expiresIn
invitation.ftl User invitation inviter, invitationUrl
inspection-assigned.ftl Inspection assignment user, inspection
inspection-reminder.ftl Inspection reminder user, inspection, dueDate
work-order-created.ftl Work order notification user, workOrder
alert-triggered.ftl Alert notification user, alert
maintenance-due.ftl Maintenance reminder user, maintenancePlan

Creating Custom Templates

<!-- templates/email/custom-notification.ftl -->
<#import "base-layout.ftl" as layout>

<@layout.base subject="Custom Notification">
  <h1>Hello ${user.firstName}!</h1>

  <p>This is a custom notification about ${entityType}.</p>

  <div class="details">
    <p><strong>Details:</strong></p>
    <ul>
      <#list details as detail>
        <li>${detail}</li>
      </#list>
    </ul>
  </div>

  <a href="${actionUrl}" class="button">Take Action</a>
</@layout.base>

Template Variables

Common variables available in all templates:

Variable Type Description
user Object Current user
user.firstName String User's first name
user.lastName String User's last name
user.email String User's email
company Object User's company
appName String Application name
appUrl String Application URL
logoUrl String Logo URL
supportEmail String Support email
currentYear Number Current year

Sending Emails

Using EmailService (Programmatic)

@Service
@RequiredArgsConstructor
public class NotificationService {

    private final EmailService emailService;

    public void notifyUser(User user, Inspection inspection) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("user", user);
        variables.put("inspection", inspection);

        emailService.sendTemplateEmail(
            user.getEmail(),
            "Inspection Assigned",
            "inspection-assigned",
            variables
        );
    }
}

Email Service Methods

// Simple text email
emailService.sendSimpleEmail(
    "user@example.com",
    "Subject",
    "Plain text body"
);

// HTML email
emailService.sendHtmlEmail(
    "user@example.com",
    "Subject",
    "<h1>HTML Content</h1>"
);

// Template email
emailService.sendTemplateEmail(
    "user@example.com",
    "Subject",
    "template-name",
    Map.of("var1", value1, "var2", value2)
);

// Email with attachments
emailService.sendEmailWithAttachment(
    "user@example.com",
    "Subject",
    "Body",
    attachment
);

// Bulk email
emailService.sendBulkEmail(
    List.of("user1@example.com", "user2@example.com"),
    "Subject",
    "Body"
);

Company-Specific Sending

// Send using company's configured provider
emailService.sendForCompany(
    companyId,
    "user@example.com",
    "Subject",
    "Body"
);

Admin API

Provider Management

Method Endpoint Description
GET /api/admin/email-providers/types List provider types
GET /api/admin/email-providers/company/{id} List company configs
GET /api/admin/email-providers/{id} Get config details
POST /api/admin/email-providers Create config
PUT /api/admin/email-providers/{id} Update config
DELETE /api/admin/email-providers/{id} Delete config
POST /api/admin/email-providers/{id}/activate Activate config
POST /api/admin/email-providers/{id}/deactivate Deactivate config
POST /api/admin/email-providers/{id}/test Test connection
POST /api/admin/email-providers/{id}/test-email Send test email

Testing Email Configuration

# Test provider connection
curl -X POST "https://api.tgm-expert.com/api/admin/email-providers/1/test" \
  -H "Authorization: Bearer $JWT_TOKEN"

# Send test email
curl -X POST "https://api.tgm-expert.com/api/admin/email-providers/1/test-email?to=test@example.com" \
  -H "Authorization: Bearer $JWT_TOKEN"

Email Administration

Method Endpoint Description
GET /api/admin/email/templates List templates
GET /api/admin/email/templates/{name}/preview Preview template
POST /api/admin/email/send Send ad-hoc email
GET /api/admin/email/stats Email statistics

Queue Management

How the Queue Works

  1. Email is added to queue with PENDING status
  2. Background worker picks up pending emails
  3. Email is sent via configured provider
  4. On success: marked as SENT
  5. On failure: retried with exponential backoff
  6. After max retries: marked as FAILED

Queue Status

curl -X GET "https://api.tgm-expert.com/api/admin/queue/email/stats" \
  -H "Authorization: Bearer $JWT_TOKEN"

Response:

{
  "data": {
    "pending": 5,
    "processing": 2,
    "sent": 1523,
    "failed": 3,
    "averageDeliveryTime": 1.2
  }
}

Retry Configuration

Setting Default Description
Max retries 3 Maximum retry attempts
Initial delay 1 minute First retry delay
Max delay 1 hour Maximum retry delay
Backoff multiplier 2 Exponential backoff factor

Manual Retry

curl -X POST "https://api.tgm-expert.com/api/admin/queue/email/{id}/retry" \
  -H "Authorization: Bearer $JWT_TOKEN"

Best Practices

1. Use Templates

Always use templates for consistent branding and easier maintenance.

2. Configure Fallback Providers

Set up at least two providers for redundancy.

Primary: SendGrid
Fallback: AWS SES

3. Monitor Delivery Rates

Regularly check email statistics and address bounces/complaints.

4. Validate Email Addresses

Validate email addresses before sending to reduce bounces.

5. Use Appropriate From Addresses

  • noreply@ for automated notifications
  • support@ for support-related emails
  • Configure SPF, DKIM, and DMARC for deliverability

6. Handle Unsubscribes

Include unsubscribe links in marketing emails and honor opt-outs.

7. Rate Limit Bulk Sends

When sending bulk emails, respect provider rate limits:

Provider Rate Limit
SendGrid 100/second
AWS SES 14/second (default)
Mailgun 300/minute

Troubleshooting

Emails Not Sending

  1. Check if email is enabled: app.email.enabled=true
  2. Verify provider configuration: GET /api/admin/email-providers/{id}
  3. Test provider connection: POST /api/admin/email-providers/{id}/test
  4. Check queue status: GET /api/admin/queue/email/stats
  5. Review application logs

High Bounce Rate

  1. Verify email addresses before sending
  2. Check for typos in recipient addresses
  3. Remove invalid addresses from lists
  4. Monitor blacklist status

Emails Going to Spam

  1. Configure SPF record for your domain
  2. Set up DKIM signing
  3. Configure DMARC policy
  4. Use consistent from addresses
  5. Avoid spam trigger words

Provider Connection Errors

  1. Verify API keys/credentials
  2. Check IP whitelist requirements
  3. Ensure network connectivity
  4. Review provider status page

Email Templates Reference

Variable Types

Type Example Access
String ${user.firstName} Direct
Number ${count} Direct
Date ${date?string('yyyy-MM-dd')} Formatted
Boolean <#if active>...</#if> Conditional
List <#list items as item>...</#list> Loop
Object ${user.company.name} Nested

Common Directives

<!-- Conditionals -->
<#if condition>
  ...
<#elseif otherCondition>
  ...
<#else>
  ...
</#if>

<!-- Loops -->
<#list items as item>
  ${item.name}
</#list>

<!-- Default values -->
${variable!"default value"}

<!-- Null checks -->
<#if variable??>
  ${variable}
</#if>

<!-- Date formatting -->
${date?string('MMMM dd, yyyy')}

<!-- Number formatting -->
${number?string('#,##0.00')}