Tenant-Specific Email Providers
Isolate provider configuration per tenant. Each tenant uses their own credentials and provider.
Multi-Tenant Architecture
Each tenant (TenantID) can configure their own email provider independently:
Tenant 1 (TenantID=1): Uses Gmail OAuth2 (email-primary)
Tenant 2 (TenantID=2): Uses AWS SES (email-primary)
Tenant 3 (TenantID=3): Uses custom SMTP server (email-primary)Database Structure
SELECT * FROM AIExt_Credentials
WHERE Name='email-primary' AND TenantID IN (1,2,3)
ORDER BY TenantID;
Results:
TenantID=1: provider=gmail, accessToken=...
TenantID=2: provider=ses, region=us-east-1
TenantID=3: provider=smtp, host=mail.company.localSetup: Tenant 1 with Gmail
INSERT INTO AIExt_Credentials (
Name, DisplayMetadata, EncryptedData, TenantID, CredentialTypeID, Enabled
) VALUES (
'email-primary', -- All tenants use same name
'{
"provider": "gmail",
"fromEmail": "user1@gmail.com",
"fromName": "Tenant 1 Company"
}',
0x..., -- Encrypted OAuth tokens
1, -- TenantID=1
3, -- OAuth2Record
1
);Setup: Tenant 2 with SES
INSERT INTO AIExt_Credentials (
Name, DisplayMetadata, EncryptedData, TenantID, CredentialTypeID, Enabled
) VALUES (
'email-primary', -- Same name!
'{
"provider": "ses",
"region": "us-east-1",
"fromEmail": "notifications@tenant2.com",
"fromName": "Tenant 2 Notifications"
}',
0x..., -- Encrypted AWS keys
2, -- TenantID=2
2, -- ApiKeyRecord
1
);Code: Same API, Different Providers
// The same code works for all tenants!
private readonly IEmailService _emailService;
private readonly ITenantSettingsService _tenantSettingsService;
public async Task SendWelcomeEmail(int tenantID, string email, CancellationToken ct)
{
// Resolve credential name from tenant settings (defaults to "email-primary")
var credentialName = await _tenantSettingsService.GetAsync<string>(
tenantID,
"EmailCredentialName",
defaultValue: "email-primary",
ct);
var result = await _emailService.SendAsync(
tenantID: tenantID,
to: email,
subject: "Welcome",
html: "<p>Welcome to our platform!</p>",
credentialName: credentialName, // Resolved per tenant from settings
ct: ct);
return result.Success;
}
// Tenant 1 call: Uses Gmail
SendWelcomeEmail(tenantID: 1, email: "user@example.com", CancellationToken.None);
// ↓ Reads TenantSettings: Key='EmailCredentialName', TenantID=1 → "email-primary"
// ↓ Looks up: AIExt_Credentials WHERE Name='email-primary' AND TenantID=1
// ↓ Finds Gmail credential
// ↓ Sends via Gmail
// Tenant 2 call: Uses SES
SendWelcomeEmail(tenantID: 2, email: "user@example.com", CancellationToken.None);
// ↓ Reads TenantSettings: Key='EmailCredentialName', TenantID=2 → "email-primary"
// ↓ Looks up: AIExt_Credentials WHERE Name='email-primary' AND TenantID=2
// ↓ Finds SES credential
// ↓ Sends via SESMulti-Credential Setup
Each tenant can have multiple named credentials and configure which one to use:
-- Tenant 1 credentials
INSERT INTO AIExt_Credentials (Name, TenantID, ...)
VALUES ('email-primary', 1, ...); -- Main email (Gmail)
INSERT INTO AIExt_Credentials (Name, TenantID, ...)
VALUES ('email-alerts', 1, ...); -- Alerts only (SMTP)
-- Configure which credential to use for welcome emails
INSERT INTO TenantSettings (TenantID, Key, Value)
VALUES (1, 'EmailCredentialName', 'email-primary');
-- In code, TenantSettings controls which credential is used:
public async Task SendWelcomeEmail(int tenantID, string email, CancellationToken ct)
{
var credentialName = await _tenantSettingsService.GetAsync<string>(
tenantID, "EmailCredentialName", "email-primary", ct);
await _emailService.SendAsync(tenantID, email, subject, html, credentialName, ct);
}
public async Task SendAlert(int tenantID, string email, CancellationToken ct)
{
// Can use a different credential if configured
var credentialName = await _tenantSettingsService.GetAsync<string>(
tenantID, "EmailAlertCredentialName", "email-alerts", ct);
await _emailService.SendAsync(tenantID, email, subject, html, credentialName, ct);
}Isolation Guarantees
- ✅ Each tenant's credentials encrypted separately
- ✅ Database index ensures fast per-tenant lookups
- ✅ No cross-tenant credential leakage possible
- ✅ Tenants can't access other tenants' credentials
- ✅ Each tenant can change provider independently
