nopSolutions/nopCommerce · error · NopException

Admin.Configuration.EmailAccounts.Fields.ClientSecret.Requir

Error message

Admin.Configuration.EmailAccounts.Fields.ClientSecret.Required

What it means

Thrown by SmtpBuilder.GetGmailCredentialsAsync when EmailAccount.ClientSecret is empty for a Gmail OAuth account. OAuth requires the client secret to exchange the auth code for tokens; without it the flow fails immediately, so it throws NopException with a localized resource message. It is the companion check to the ClientId validation.

Source

Thrown at src/Libraries/Nop.Services/Messages/SmtpBuilder.cs:56

        _emailAccountSettings = emailAccountSettings;
        _emailAccountService = emailAccountService;
        _localizationService = localizationService;
        _fileProvider = fileProvider;
    }

    #endregion

    #region Utilities

    protected virtual async Task<SaslMechanism> GetGmailCredentialsAsync(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"));

        var tokenFilePath = _fileProvider.MapPath(NopMessageDefaults.GmailAuthStorePath);
        var credentialRoot = _fileProvider.Combine(tokenFilePath, emailAccount.Email);

        var codeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
        {
            ClientSecrets = new ClientSecrets
            {
                ClientId = emailAccount.ClientId,
                ClientSecret = emailAccount.ClientSecret
            },
            Scopes = NopMessageDefaults.GmailScopes,
            DataStore = new FileDataStore(credentialRoot, true)
        });

        var authCode = new AuthorizationCodeWebApp(codeFlow, null, null);

        var authResult = await authCode.AuthorizeAsync(emailAccount.Email, CancellationToken.None);

View on GitHub (pinned to 64bdf2ff08)

Solutions

  1. In admin, set the EmailAccount ClientSecret from the Google Cloud OAuth client.
  2. Re-run the OAuth authorization so a token store is created with valid credentials.
  3. Confirm both ClientId and ClientSecret are non-empty before marking the account active.
  4. Ensure the Google OAuth client type is 'Desktop'/'Web' with the secret available.

Example fix

// before - only ClientId set

// after - require both before the account can send
if (string.IsNullOrEmpty(account.ClientId) || string.IsNullOrEmpty(account.ClientSecret))
    return Error("Gmail OAuth requires both ClientId and ClientSecret.");
Defensive patterns

Strategy: validation

Validate before calling

if (account.Host.Contains("gmail", StringComparison.OrdinalIgnoreCase)
    && string.IsNullOrEmpty(account.ClientSecret))
    return Error("Set the Gmail OAuth ClientSecret for this email account.");

Type guard

static bool HasGmailCredentials(EmailAccount a)
    => !string.IsNullOrEmpty(a.ClientId) && !string.IsNullOrEmpty(a.ClientSecret);

Try / catch

try { await smtpBuilder.BuildAsync(account); }
catch (NopException ex) when (ex.Message.Contains("EmailAccounts.Fields.ClientSecret.Required"))
{ /* surface a localized 'set ClientSecret' message to the admin */ }

Prevention

When it happens

Trigger: A Gmail-configured EmailAccount with a populated ClientId but blank ClientSecret; sending mail triggers GetGmailCredentialsAsync.

Common situations: Admin pasted only the ClientId; OAuth client secret not copied from Google Cloud; redeploy without the secret in config.

Related errors


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