Secure Exchanges SDK Documentation
Search Results for

    Show / Hide Table of Contents

    Best Practices

    Recommendations for using the SDK securely and efficiently.

    Credential Security

    Never Store in Plain Text

    // BAD - Never do this
    public static Guid ApiPassword = new Guid("xxxx-xxxx-xxxx");
    
    // GOOD - Use environment variables
    public static Guid ApiPassword => new Guid(Environment.GetEnvironmentVariable("SE_API_PASSWORD"));
    
    // GOOD - Use a secrets manager (Azure Key Vault, AWS Secrets Manager)
    public static Guid ApiPassword => secretsManager.GetSecret("SecureExchanges:ApiPassword");
    

    Protect Configuration Files

    <!-- Encrypt sensitive sections of web.config -->
    <appSettings configProtectionProvider="DataProtectionConfigurationProvider">
      <EncryptedData>...</EncryptedData>
    </appSettings>
    

    Use DataProtectionHelper to Store Credentials (Recommended)

    The SDK provides DataProtectionHelper which uses the Windows DPAPI with the machine key (DataProtectionScope.LocalMachine). Data is encrypted and can only be decrypted on the same machine.

    using SecureExchangesSDK.Helpers;
    using SecureExchangesSDK.Models.Transport;
    
    // 1. Create a model to store your credentials
    public class ApiCredentials
    {
        public Guid Serial { get; set; }
        public Guid ApiUser { get; set; }
        public Guid ApiPassword { get; set; }
        public string Endpoint { get; set; }
    }
    
    // 2. Save credentials (encrypted with machine key)
    public void SaveCredentials(ApiCredentials credentials, string filePath)
    {
        // Serializes to JSON then encrypts with DPAPI (machine key)
        string json = SerializationHelper.SerializeToJson(credentials);
        DataProtectionHelper<ApiCredentials>.ProtectAndSaveToFile(json, filePath);
    }
    
    // 3. Load credentials
    public ApiCredentials LoadCredentials(string filePath)
    {
        // Decrypts and deserializes automatically
        // Uses in-memory cache for subsequent calls
        return DataProtectionHelper<ApiCredentials>.GetUnprotectedAsType(filePath);
    }
    
    // 4. Usage in your application
    public class SecureMessageService
    {
        private readonly ApiCredentials _credentials;
        private const string CredentialsPath = @"C:\ProgramData\MyApp\credentials.dat";
    
        public SecureMessageService()
        {
            _credentials = DataProtectionHelper<ApiCredentials>.GetUnprotectedAsType(CredentialsPath);
        }
    
        public string SendMessage(string recipientEmail, string subject, string body)
        {
            var recipients = new List<RecipientInfo> { new RecipientInfo { Email = recipientEmail } };
    
            var args = new MutliRecipientArgs(
                _credentials.Endpoint,
                _credentials.Serial,
                _credentials.ApiUser,
                _credentials.ApiPassword,
                recipients, body, subject,
                null, null, SendMethodEnum.onlyEmail,
                false, true, true, "en-CA", 5, 1440
            );
    
            var response = MessageHelper.MultiRecipientMessage(args);
            return response.Status == 200 ? response.RecipientsAnswer[0].Answer.URL : null;
        }
    }
    
    Note

    How DataProtectionHelper works:

    • Uses ProtectedData.Protect() with DataProtectionScope.LocalMachine
    • Data is encrypted with the Windows machine key
    • Only processes running on the same machine can decrypt
    • Includes in-memory caching to avoid repeated file reads
    • Supports an optional entropy parameter for additional security
    Tip

    For enhanced security, you can add custom entropy:

    byte[] entropy = Encoding.UTF8.GetBytes("MySecretApplication");
    DataProtectionHelper<ApiCredentials>.ProtectAndSaveToFile(json, filePath, entropy);
    var credentials = DataProtectionHelper<ApiCredentials>.GetUnprotectedAsType(filePath, true, entropy);
    

    Performance

    Reuse Instances

    // GOOD - Singleton service
    public class MessageService
    {
        private readonly Guid _serial;
        private readonly Guid _apiUser;
        private readonly Guid _apiPassword;
    
        public MessageService(IConfiguration config)
        {
            _serial = new Guid(config["SecureExchanges:Serial"]);
            _apiUser = new Guid(config["SecureExchanges:ApiUser"]);
            _apiPassword = new Guid(config["SecureExchanges:ApiPassword"]);
        }
    
        public string Send(...) { ... }
    }
    
    // Register as singleton
    services.AddSingleton<MessageService>();
    

    Limit In-Memory Files

    // For large files, use FilesPath instead of FileArgs
    if (fileSize > 50 * 1024 * 1024) // > 50 MB
    {
        args.FilesPath = new List<string> { filePath };
    }
    else
    {
        args.BinaryFiles = new List<FileArgs> { new FileArgs(File.ReadAllBytes(filePath), ...) };
    }
    

    Error Handling

    Always Check Status

    var response = MessageHelper.MultiRecipientMessage(args);
    
    // ALWAYS check before accessing data
    if (response.Status != 200)
    {
        _logger.LogError($"Error {response.Status}: {response.Data}");
        throw new Exception(response.Data);
    }
    
    // Now you can access the data
    var url = response.RecipientsAnswer[0].Answer.URL;
    

    Implement Retries

    public async Task<T> ExecuteWithRetry<T>(Func<T> action, int maxRetries = 3)
    {
        for (int i = 0; i < maxRetries; i++)
        {
            try
            {
                return action();
            }
            catch (Exception ex) when (IsTransientError(ex))
            {
                if (i == maxRetries - 1) throw;
                await Task.Delay(1000 * (i + 1));
            }
        }
        throw new Exception("Max retries exceeded");
    }
    

    Logging

    Log Important Operations

    public string SendSecureMessage(string recipient, string subject)
    {
        _logger.LogInformation("Sending message to {Recipient}", recipient);
    
        var response = MessageHelper.MultiRecipientMessage(args);
    
        if (response.Status == 200)
        {
            _logger.LogInformation("Message sent. URL: {Url}", response.RecipientsAnswer[0].Answer.URL);
        }
        else
        {
            _logger.LogError("Send failed. Status: {Status}, Details: {Details}",
                response.Status, response.Data);
        }
    
        return response.Status == 200 ? response.RecipientsAnswer[0].Answer.URL : null;
    }
    

    Never Log Sensitive Data

    // BAD
    _logger.LogDebug($"Sending with password: {args.Password}");
    
    // GOOD
    _logger.LogDebug("Sending with password: [MASKED]");
    

    Data Validation

    Validate Emails Before Sending

    using SecureExchangesSDK.Helpers;
    
    foreach (var recipient in recipients)
    {
        if (!EmailHelper.IsValidEmail(recipient.Email))
        {
            throw new ArgumentException($"Invalid email: {recipient.Email}");
        }
    }
    

    Validate Phone Numbers

    if (!string.IsNullOrEmpty(recipient.Phone))
    {
        if (!PhoneHelper.IsValidPhoneNumber(recipient.Phone))
        {
            throw new ArgumentException($"Invalid phone: {recipient.Phone}");
        }
    }
    

    See Also

    • Status Codes
    • Architecture
    In this article
    Back to top Secure Exchanges Inc. - Documentation