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

  1. In admin, set the EmailAccount TenantId to the Azure AD Directory (tenant) ID.
  2. Use 'common' or the specific tenant GUID per the desired token audience.
  3. Confirm ClientId, ClientSecret, and TenantId are all populated.
  4. 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

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


AI-assisted analysis of nopSolutions/nopCommerce@64bdf2ff08 (2026-08-13). Data as JSON: /api/errors/7d3cd14b36e98a46. Report an issue: GitHub.