nopSolutions/nopCommerce · error · NopException
Admin.Configuration.EmailAccounts.Fields.TenantId.Required
Error message
Admin.Configuration.EmailAccounts.Fields.TenantId.Required
What it means
Thrown by SmtpBuilder.GetExchangeCredentialsAsync when EmailAccount.TenantId is empty for an Exchange/MSAL account. The authority URL is built with string.Format(MSALTenantPattern, tenantId); an empty tenantId would point at the wrong authority, so the method throws NopException with a localized resource message before constructing the client.
Source
Thrown at src/Libraries/Nop.Services/Messages/SmtpBuilder.cs:96
if (authResult.Credential.Token?.IsStale == true)
await authResult.Credential.RefreshTokenAsync(CancellationToken.None);
return new SaslMechanismOAuth2(authResult.Credential.UserId, authResult.Credential.Token.AccessToken);
}
protected virtual async Task<SaslMechanism> GetExchangeCredentialsAsync(EmailAccount emailAccount)
{
ArgumentNullException.ThrowIfNull(emailAccount);
if (string.IsNullOrEmpty(emailAccount.ClientId))
throw new NopException(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Fields.ClientId.Required"));
if (string.IsNullOrEmpty(emailAccount.ClientSecret))
throw new NopException(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Fields.ClientSecret.Required"));
if (string.IsNullOrEmpty(emailAccount.TenantId))
throw new NopException(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Fields.TenantId.Required"));
var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(emailAccount.ClientId)
.WithAuthority(string.Format(NopMessageDefaults.MSALTenantPattern, emailAccount.TenantId))
.WithClientSecret(emailAccount.ClientSecret)
.Build();
var authToken = await confidentialClientApplication.AcquireTokenForClient(NopMessageDefaults.MSALScopes).ExecuteAsync();
return new SaslMechanismOAuth2(emailAccount.Email, authToken.AccessToken);
}
#endregion
#region Methods
/// <summary>
/// Create a new SMTP client for a specific email account
/// </summary>View on GitHub (pinned to 64bdf2ff08)
Solutions
- In admin, set the EmailAccount TenantId to the Azure AD Directory (tenant) ID.
- Use 'common' or the specific tenant GUID per the desired token audience.
- Confirm ClientId, ClientSecret, and TenantId are all populated.
- Verify the tenant matches where the app registration lives.
Example fix
// before - tenant blank
// after - require tenant
if (string.IsNullOrWhiteSpace(account.TenantId))
return Error("Exchange OAuth requires the Azure AD TenantId."); Defensive patterns
Strategy: validation
Validate before calling
if (IsExchangeAccount(account) && string.IsNullOrEmpty(account.TenantId))
return Error("Set the Azure AD TenantId for this email account."); Type guard
static bool HasExchangeCredentials(EmailAccount a)
=> !string.IsNullOrEmpty(a.ClientId)
&& !string.IsNullOrEmpty(a.ClientSecret)
&& !string.IsNullOrEmpty(a.TenantId); Try / catch
try { await smtpBuilder.BuildAsync(account); }
catch (NopException ex) when (ex.Message.Contains("EmailAccounts.Fields.TenantId.Required"))
{ /* surface localized 'set TenantId' message */ } Prevention
- Copy the Azure AD Directory (tenant) ID into the account.
- Validate all three MSAL fields together when saving.
- Ensure the tenant matches the app registration.
When it happens
Trigger: An Exchange-configured EmailAccount with ClientId/Secret set but blank TenantId; sending mail triggers GetExchangeCredentialsAsync.
Common situations: Admin registered the Azure app but did not copy the Directory (tenant) ID; multi-tenant confusion; redeploy missing tenant config.
Related errors
- Admin.Configuration.EmailAccounts.Fields.ClientId.Required
- Admin.Configuration.EmailAccounts.Fields.ClientSecret.Requir
- Failed to obtain user credentials for the authorization serv
- Email account could not be loaded
- Client ID is not set
AI-assisted analysis of nopSolutions/nopCommerce@64bdf2ff08 (2026-08-13).
Data as JSON: /api/errors/7d3cd14b36e98a46.
Report an issue: GitHub.