Modern .NET SDK supporting .NET 6.0+, ASP.NET Core integration, and Blazor compatibility.
# Package Manager Console
Install-Package SparkMailr.SDK
# .NET CLI
dotnet add package SparkMailr.SDK
# PackageReference (in .csproj)
<PackageReference Include="SparkMailr.SDK" Version="1.0.0" />
using SparkMailr;
using SparkMailr.Models;
// Initialize client
var client = new SparkMailrClient(new SparkMailrOptions
{
ApiKey = "your-api-key"
});
try
{
// Send email
var request = new SendEmailRequest
{
To = new[] { "user@example.com" },
From = "noreply@yourapp.com",
Subject = "Welcome to SparkMailr!",
Html = "<h1>Hello World</h1>",
Text = "Hello World"
};
var response = await client.Emails.SendAsync(request);
Console.WriteLine($"Email sent: {response.Id}");
}
catch (SparkMailrException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
using SparkMailr;
// Full configuration
var options = new SparkMailrOptions
{
ApiKey = "your-api-key",
BaseUrl = "https://api.sparkmailr.com", // Optional
Timeout = TimeSpan.FromSeconds(30), // Request timeout
MaxRetries = 3, // Max retry attempts
RetryDelay = TimeSpan.FromSeconds(1), // Delay between retries
Debug = false // Enable debug logging
};
var client = new SparkMailrClient(options);
// Configuration from appsettings.json
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var sparkMailrOptions = new SparkMailrOptions();
configuration.GetSection("SparkMailr").Bind(sparkMailrOptions);
var client = new SparkMailrClient(sparkMailrOptions);
using SparkMailr.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add SparkMailr services
builder.Services.AddSparkMailr(options =>
{
options.ApiKey = builder.Configuration["SparkMailr:ApiKey"];
options.BaseUrl = builder.Configuration["SparkMailr:BaseUrl"];
options.Timeout = TimeSpan.FromSeconds(30);
options.Debug = builder.Environment.IsDevelopment();
});
// Add other services
builder.Services.AddControllers();
builder.Services.AddScoped<IEmailService, EmailService>();
var app = builder.Build();
// Configure pipeline
app.UseRouting();
app.MapControllers();
app.Run();
{
"SparkMailr": {
"ApiKey": "your-api-key",
"BaseUrl": "https://api.sparkmailr.com",
"Timeout": "00:00:30",
"MaxRetries": 3,
"Debug": false
},
"Logging": {
"LogLevel": {
"SparkMailr": "Information"
}
}
}
public interface IEmailService
{
Task<string> SendWelcomeEmailAsync(string userEmail, string userName);
Task<string> SendPasswordResetAsync(string userEmail, string resetToken);
Task<BulkEmailResult> SendBulkEmailsAsync(BulkEmailRequest request);
}
public class EmailService : IEmailService
{
private readonly ISparkMailrClient _sparkMailrClient;
private readonly ILogger<EmailService> _logger;
public EmailService(ISparkMailrClient sparkMailrClient, ILogger<EmailService> logger)
{
_sparkMailrClient = sparkMailrClient;
_logger = logger;
}
public async Task<string> SendWelcomeEmailAsync(string userEmail, string userName)
{
try
{
var request = new SendEmailRequest
{
To = new[] { userEmail },
From = "noreply@yourapp.com",
TemplateId = "welcome-template",
TemplateData = new Dictionary<string, object>
{
["name"] = userName,
["activationLink"] = "https://app.example.com/activate",
["supportEmail"] = "support@yourapp.com"
}
};
var response = await _sparkMailrClient.Emails.SendAsync(request);
_logger.LogInformation("Welcome email sent to {Email}: {EmailId}", userEmail, response.Id);
return response.Id;
}
catch (SparkMailrException ex)
{
_logger.LogError(ex, "Failed to send welcome email to {Email}", userEmail);
throw new EmailServiceException($"Failed to send welcome email: {ex.Message}", ex);
}
}
public async Task<string> SendPasswordResetAsync(string userEmail, string resetToken)
{
var request = new SendEmailRequest
{
To = new[] { userEmail },
From = "noreply@yourapp.com",
Subject = "Password Reset Request",
TemplateId = "password-reset-template",
TemplateData = new Dictionary<string, object>
{
["resetLink"] = $"https://app.example.com/reset-password?token={resetToken}",
["expirationTime"] = DateTime.UtcNow.AddHours(1).ToString("yyyy-MM-dd HH:mm:ss UTC")
}
};
var response = await _sparkMailrClient.Emails.SendAsync(request);
return response.Id;
}
}
[ApiController]
[Route("api/[controller]")]
public class EmailController : ControllerBase
{
private readonly IEmailService _emailService;
private readonly ILogger<EmailController> _logger;
public EmailController(IEmailService emailService, ILogger<EmailController> logger)
{
_emailService = emailService;
_logger = logger;
}
[HttpPost("send-welcome")]
public async Task<IActionResult> SendWelcomeEmail([FromBody] SendWelcomeRequest request)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
try
{
var emailId = await _emailService.SendWelcomeEmailAsync(request.Email, request.Name);
return Ok(new { Success = true, EmailId = emailId });
}
catch (EmailServiceException ex)
{
_logger.LogError(ex, "Failed to send welcome email");
return StatusCode(500, new { Success = false, Error = ex.Message });
}
}
[HttpPost("send-bulk")]
public async Task<IActionResult> SendBulkEmails([FromBody] BulkEmailRequestDto request)
{
var bulkRequest = new BulkEmailRequest
{
From = request.From,
TemplateId = request.TemplateId,
Recipients = request.Recipients.Select(r => new BulkEmailRecipient
{
To = r.To,
TemplateData = r.TemplateData
}).ToList()
};
var result = await _emailService.SendBulkEmailsAsync(bulkRequest);
return Ok(new { Success = true, BatchId = result.BatchId, Count = result.Count });
}
[HttpGet("status/{emailId}")]
public async Task<IActionResult> GetEmailStatus(string emailId)
{
try
{
var status = await _sparkMailrClient.Emails.GetStatusAsync(emailId);
return Ok(status);
}
catch (SparkMailrNotFoundException)
{
return NotFound(new { Error = "Email not found" });
}
}
}
// Send email with complex template data
var templateData = new Dictionary<string, object>
{
["user"] = new
{
FirstName = "John",
LastName = "Doe",
Plan = "Premium",
JoinDate = DateTime.UtcNow.ToString("yyyy-MM-dd")
},
["features"] = new[] { "Feature 1", "Feature 2", "Feature 3" },
["activationLink"] = "https://app.example.com/activate/123",
["company"] = new
{
Name = "Acme Corp",
SupportEmail = "support@acme.com"
}
};
var request = new SendEmailRequest
{
To = new[] { "user@example.com" },
From = "noreply@yourapp.com",
TemplateId = "welcome-template",
TemplateData = templateData
};
var response = await client.Emails.SendAsync(request);
Console.WriteLine($"Template email sent: {response.Id}");
// Prepare bulk email recipients
var recipients = new List<BulkEmailRecipient>
{
new BulkEmailRecipient
{
To = "user1@example.com",
TemplateData = new Dictionary<string, object>
{
["name"] = "User 1",
["promoCode"] = "ABC123"
}
},
new BulkEmailRecipient
{
To = "user2@example.com",
TemplateData = new Dictionary<string, object>
{
["name"] = "User 2",
["promoCode"] = "DEF456"
}
}
};
var bulkRequest = new BulkEmailRequest
{
From = "newsletter@yourapp.com",
TemplateId = "newsletter-template",
Recipients = recipients
};
var bulkResponse = await client.Emails.SendBulkAsync(bulkRequest);
Console.WriteLine($"Bulk batch: {bulkResponse.BatchId}");
// Get email status
var emailId = "em_1234567890";
var status = await client.Emails.GetStatusAsync(emailId);
Console.WriteLine($"Status: {status.Status}");
Console.WriteLine($"Delivered: {status.DeliveredAt}");
Console.WriteLine($"Opened: {status.OpenedAt}");
// Get analytics for date range
var analyticsRequest = new AnalyticsRequest
{
StartDate = DateTime.UtcNow.AddDays(-30),
EndDate = DateTime.UtcNow
};
var analytics = await client.Analytics.GetEmailStatsAsync(analyticsRequest);
Console.WriteLine($"Total sent: {analytics.Sent}");
Console.WriteLine($"Delivery rate: {analytics.DeliveryRate:P}");
Console.WriteLine($"Open rate: {analytics.OpenRate:P}");
Console.WriteLine($"Click rate: {analytics.ClickRate:P}");
using SparkMailr.Exceptions;
try
{
var request = new SendEmailRequest
{
To = new[] { "invalid-email" },
From = "noreply@yourapp.com",
Subject = "Test Email"
};
var response = await client.Emails.SendAsync(request);
}
catch (AuthenticationException ex)
{
Console.WriteLine($"Authentication failed: {ex.Message}");
}
catch (RateLimitException ex)
{
Console.WriteLine($"Rate limited - retry after: {ex.RetryAfter} seconds");
// Implement exponential backoff
await Task.Delay(TimeSpan.FromSeconds(ex.RetryAfter));
// Retry logic here
}
catch (ValidationException ex)
{
Console.WriteLine("Validation failed:");
foreach (var (field, errors) in ex.ValidationErrors)
{
Console.WriteLine($" {field}: {string.Join(", ", errors)}");
}
}
catch (NetworkException ex)
{
Console.WriteLine($"Network error: {ex.Message}");
}
catch (SparkMailrException ex)
{
Console.WriteLine($"SparkMailr error: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
// Program.cs (Blazor Server)
using SparkMailr.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
// Add SparkMailr
builder.Services.AddSparkMailr(options =>
{
options.ApiKey = builder.Configuration["SparkMailr:ApiKey"];
});
var app = builder.Build();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
// ContactForm.razor
@page "/contact"
@inject ISparkMailrClient SparkMailrClient
@inject IJSRuntime JSRuntime
<h3>Contact Form</h3>
<EditForm Model="contactModel" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="name">Name:</label>
<InputText id="name" class="form-control" @bind-Value="contactModel.Name" />
<ValidationMessage For="@(() => contactModel.Name)" />
</div>
<div class="form-group">
<label for="email">Email:</label>
<InputText id="email" class="form-control" @bind-Value="contactModel.Email" />
<ValidationMessage For="@(() => contactModel.Email)" />
</div>
<div class="form-group">
<label for="message">Message:</label>
<InputTextArea id="message" class="form-control" @bind-Value="contactModel.Message" />
<ValidationMessage For="@(() => contactModel.Message)" />
</div>
<button type="submit" class="btn btn-primary" disabled="@isSubmitting">
@if (isSubmitting)
{
<span class="spinner-border spinner-border-sm" role="status"></span>
Sending...
}
else
{
Send Message
}
</button>
</EditForm>
@code {
private ContactModel contactModel = new();
private bool isSubmitting = false;
private async Task HandleSubmit()
{
isSubmitting = true;
try
{
var request = new SendEmailRequest
{
To = new[] { "admin@yourapp.com" },
From = "noreply@yourapp.com",
Subject = $"Contact form from {contactModel.Name}",
Html = $@"
<h2>Contact Form Submission</h2>
<p><strong>Name:</strong> {contactModel.Name}</p>
<p><strong>Email:</strong> {contactModel.Email}</p>
<p><strong>Message:</strong></p>
<p>{contactModel.Message}</p>
"
};
var response = await SparkMailrClient.Emails.SendAsync(request);
await JSRuntime.InvokeVoidAsync("alert", "Message sent successfully!");
// Reset form
contactModel = new ContactModel();
}
catch (Exception ex)
{
await JSRuntime.InvokeVoidAsync("alert", $"Error: {ex.Message}");
}
finally
{
isSubmitting = false;
}
}
public class ContactModel
{
[Required]
public string Name { get; set; } = "";
[Required, EmailAddress]
public string Email { get; set; } = "";
[Required]
public string Message { get; set; } = "";
}
}
using Xunit;
using Moq;
using FluentAssertions;
using SparkMailr;
using SparkMailr.Models;
public class EmailServiceTests
{
private readonly Mock<ISparkMailrClient> _mockClient;
private readonly EmailService _emailService;
public EmailServiceTests()
{
_mockClient = new Mock<ISparkMailrClient>();
var mockLogger = new Mock<ILogger<EmailService>>();
_emailService = new EmailService(_mockClient.Object, mockLogger.Object);
}
[Fact]
public async Task SendWelcomeEmailAsync_ShouldReturnEmailId_WhenSuccessful()
{
// Arrange
var userEmail = "test@example.com";
var userName = "Test User";
var expectedEmailId = "em_123456";
var mockEmailsClient = new Mock<IEmailsClient>();
_mockClient.Setup(x => x.Emails).Returns(mockEmailsClient.Object);
mockEmailsClient
.Setup(x => x.SendAsync(It.IsAny<SendEmailRequest>()))
.ReturnsAsync(new EmailResponse { Id = expectedEmailId, Status = "queued" });
// Act
var result = await _emailService.SendWelcomeEmailAsync(userEmail, userName);
// Assert
result.Should().Be(expectedEmailId);
mockEmailsClient.Verify(x => x.SendAsync(It.Is<SendEmailRequest>(r =>
r.To.Contains(userEmail) &&
r.TemplateId == "welcome-template" &&
r.TemplateData.ContainsKey("name")
)), Times.Once);
}
[Fact]
public async Task SendWelcomeEmailAsync_ShouldThrowEmailServiceException_WhenSparkMailrFails()
{
// Arrange
var userEmail = "test@example.com";
var userName = "Test User";
var mockEmailsClient = new Mock<IEmailsClient>();
_mockClient.Setup(x => x.Emails).Returns(mockEmailsClient.Object);
mockEmailsClient
.Setup(x => x.SendAsync(It.IsAny<SendEmailRequest>()))
.ThrowsAsync(new SparkMailrException("API Error"));
// Act & Assert
await Assert.ThrowsAsync<EmailServiceException>(() =>
_emailService.SendWelcomeEmailAsync(userEmail, userName));
}
}
// Integration test with TestServer
public class EmailControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public EmailControllerIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async Task SendWelcomeEmail_ShouldReturnOk_WhenRequestIsValid()
{
// Arrange
var request = new SendWelcomeRequest
{
Email = "test@example.com",
Name = "Test User"
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/api/email/send-welcome", content);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<SendEmailResponse>(responseContent);
result.Success.Should().BeTrue();
result.EmailId.Should().NotBeNullOrEmpty();
}
}