Documentation

C# SDK

.NET 8.0+
NuGet

Send emails with C# using the official Laneful C# SDK. Built for modern .NET applications with comprehensive features and type safety.

Full Feature Support

Templates, attachments, tracking, webhooks

Type Safe

Built-in validation & error handling

Modern C#

C# 12.0+ with async/await

Prerequisites

To get the most out of this guide, you'll need:

  • • .NET 8.0 or higher
  • • C# 12.0 or higher
  • • A Laneful account with API key
  • • Verified domain for sending emails

Installation

NuGet Package

Install the package using the .NET CLI:

dotnet add package Laneful.CSharp

Quick Start

Send your first email in minutes:

Send Simple Text Email

using Laneful;
using Laneful.Models;
using Laneful.Exceptions;

// Create client
var client = new LanefulClient(
    "https://your-endpoint.send.laneful.net",
    "your-auth-token"
);

// Create email
var email = new Email.Builder()
    .From(new Address("sender@example.com", "Your Name"))
    .To(new Address("recipient@example.com", "Recipient Name"))
    .Subject("Hello from Laneful!")
    .TextContent("This is a simple test email sent using the Laneful C# SDK.")
    .Build();

// Send email
try
{
    var response = await client.SendEmailAsync(email);
    Console.WriteLine("✓ Email sent successfully!");
    Console.WriteLine($"Response: {response}");
}
catch (ValidationException ex)
{
    Console.WriteLine($"✗ Validation error: {ex.Message}");
}
catch (ApiException ex)
{
    Console.WriteLine($"✗ API error: {ex.Message}");
    Console.WriteLine($"Status code: {ex.StatusCode}");
}
catch (HttpException ex)
{
    Console.WriteLine($"✗ HTTP error: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"✗ Unexpected error: {ex.Message}");
}

Examples

Common use cases and patterns:

HTML Email with Tracking

// Create tracking settings
var tracking = new TrackingSettings(opens: true, clicks: true, unsubscribes: true);

var email = new Email.Builder()
    .From(new Address("sender@example.com", "Your Name"))
    .To(new Address("recipient@example.com", "Recipient Name"))
    .Subject("HTML Email with Tracking")
    .HtmlContent("<h1>Welcome!</h1><p>This is an <strong>HTML email</strong> with tracking enabled.</p>")
    .TextContent("Welcome! This is an HTML email with tracking enabled.")
    .Tracking(tracking)
    .Tag("welcome-email")
    .Build();

var response = await client.SendEmailAsync(email);

Template Email

var email = new Email.Builder()
    .From(new Address("sender@example.com"))
    .To(new Address("user@example.com"))
    .TemplateId("welcome-template")
    .TemplateData(new Dictionary<string, object>
    {
        ["name"] = "John Doe",
        ["company"] = "Acme Corporation",
        ["activation_link"] = "https://example.com/activate"
    })
    .Build();

var response = await client.SendEmailAsync(email);

Email with Attachments

// Create attachment from file
var attachment = Attachment.FromFile("/path/to/document.pdf");

var email = new Email.Builder()
    .From(new Address("sender@example.com"))
    .To(new Address("user@example.com"))
    .Subject("Document Attached")
    .TextContent("Please find the document attached.")
    .Attachment(attachment)
    .Build();

var response = await client.SendEmailAsync(email);

Multiple Recipients with Reply-To

var email = new Email.Builder()
    .From(new Address("sender@example.com", "Your Name"))
    .To(new Address("user1@example.com", "User One"))
    .To(new Address("user2@example.com", "User Two"))
    .Cc(new Address("cc@example.com", "CC Recipient"))
    .Bcc(new Address("bcc@example.com", "BCC Recipient"))
    .ReplyTo(new Address("reply@example.com", "Reply To"))
    .Subject("Email to Multiple Recipients")
    .TextContent("This email is being sent to multiple recipients.")
    .Build();

var response = await client.SendEmailAsync(email);

Scheduled Email

// Schedule for 24 hours from now
var sendTime = DateTimeOffset.UtcNow.AddHours(24).ToUnixTimeSeconds();

var email = new Email.Builder()
    .From(new Address("sender@example.com"))
    .To(new Address("user@example.com"))
    .Subject("Scheduled Email")
    .TextContent("This email was scheduled.")
    .SendTime(sendTime)
    .Build();

var response = await client.SendEmailAsync(email);

Batch Email Sending

var emails = new[]
{
    new Email.Builder()
        .From(new Address("sender@example.com"))
        .To(new Address("user1@example.com"))
        .Subject("Email 1")
        .TextContent("First email content.")
        .Build(),
    new Email.Builder()
        .From(new Address("sender@example.com"))
        .To(new Address("user2@example.com"))
        .Subject("Email 2")
        .TextContent("Second email content.")
        .Build()
};

var response = await client.SendEmailsAsync(emails);

Webhook Handler Example

using Microsoft.AspNetCore.Mvc;
using Laneful.Webhooks;

[ApiController]
[Route("api/[controller]")]
public class WebhookController : ControllerBase
{
    private readonly string _webhookSecret;
    private readonly ILogger<WebhookController> _logger;

    public WebhookController(ILogger<WebhookController> logger)
    {
        _logger = logger;
        _webhookSecret = Environment.GetEnvironmentVariable("LANEFUL_WEBHOOK_SECRET") 
            ?? throw new InvalidOperationException("LANEFUL_WEBHOOK_SECRET is required");
    }

    [HttpPost("laneful")]
    public async Task<IActionResult> HandleWebhook()
    {
        try
        {
            // Read raw payload
            using var reader = new StreamReader(Request.Body);
            var payload = await reader.ReadToEndAsync();
            
            if (string.IsNullOrWhiteSpace(payload))
            {
                return BadRequest(new { error = "Empty payload received" });
            }

            // Convert headers to dictionary
            var headers = Request.Headers.ToDictionary(
                h => h.Key,
                h => h.Value.ToString(),
                StringComparer.OrdinalIgnoreCase
            );

            // Extract signature from headers
            var signature = WebhookVerifier.ExtractSignatureFromHeaders(headers);
            if (string.IsNullOrEmpty(signature))
            {
                return Unauthorized(new { error = "Missing signature" });
            }

            // Verify signature
            if (!WebhookVerifier.VerifySignature(_webhookSecret, payload, signature))
            {
                return Unauthorized(new { error = "Invalid signature" });
            }

            // Parse and validate payload
            var webhookData = WebhookVerifier.ParseWebhookPayload(payload);

            // Process events
            foreach (var eventData in webhookData.Events)
            {
                var eventType = eventData["event"]?.ToString();
                var email = eventData["email"]?.ToString();

                switch (eventType)
                {
                    case "delivery":
                        _logger.LogInformation("Email delivered to: {Email}", email);
                        break;
                    case "open":
                        _logger.LogInformation("Email opened by: {Email}", email);
                        break;
                    case "click":
                        var url = eventData["url"]?.ToString() ?? "Unknown URL";
                        _logger.LogInformation("Link clicked by {Email}: {Url}", email, url);
                        break;
                    case "bounce":
                        var isHard = eventData["is_hard"] as bool? ?? false;
                        _logger.LogInformation("Email bounced ({Type}) for: {Email}", 
                            isHard ? "hard" : "soft", email);
                        break;
                }
            }

            return Ok(new
            {
                status = "success",
                processed = webhookData.Events.Count,
                mode = webhookData.IsBatch ? "batch" : "single"
            });
        }
        catch (ArgumentException ex)
        {
            return BadRequest(new { error = $"Invalid payload: {ex.Message}" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing webhook");
            return StatusCode(500, new { error = "Processing error" });
        }
    }
}

API Reference

LanefulClient

Constructors

  • LanefulClient(string baseUrl, string authToken) - Default timeout (30 seconds)
  • LanefulClient(string baseUrl, string authToken, TimeSpan timeout) - Custom timeout
  • LanefulClient(string baseUrl, string authToken, HttpClient httpClient) - Custom HTTP client
  • LanefulClient(string baseUrl, string authToken, ILogger<LanefulClient> logger) - With logger

Methods

  • Task<Dictionary<string, object>> SendEmailAsync(Email email) - Send single email
  • Task<Dictionary<string, object>> SendEmailsAsync(IEnumerable<Email> emails) - Send multiple emails

Email.Builder

Required Fields

  • From(Address from) - Sender address

Optional Fields

  • To(Address to) / To(string email, string? name) - Recipient addresses
  • Cc(Address cc) / Cc(string email, string? name) - CC addresses
  • Bcc(Address bcc) / Bcc(string email, string? name) - BCC addresses
  • ReplyTo(Address? replyTo) / ReplyTo(string email, string? name) - Reply-to address
  • Subject(string subject) - Email subject
  • TextContent(string? textContent) - Plain text content
  • HtmlContent(string? htmlContent) - HTML content
  • TemplateId(string? templateId) - Template ID
  • TemplateData(Dictionary<string, object>? templateData) - Template data
  • Attachment(Attachment attachment) - File attachments
  • Headers(Dictionary<string, string>? headers) - Custom headers
  • SendTime(long? sendTime) - Scheduled send time (Unix timestamp)
  • WebhookData(Dictionary<string, string>? webhookData) - Webhook data
  • Tag(string? tag) - Email tag for categorization
  • Tracking(TrackingSettings? tracking) - Tracking settings

Utility Classes

  • Address(string email, string? name) - Email address with optional name
  • Attachment.FromFile(string filePath) - Create attachment from file
  • Attachment(string filename, string contentType, string content) - Create attachment from raw data
  • TrackingSettings(bool opens, bool clicks, bool unsubscribes) - Email tracking configuration

WebhookVerifier

Signature Verification

  • bool VerifySignature(string secret, string payload, string signature) - Verifies webhook signature (supports sha256= prefix)
  • string GenerateSignature(string secret, string payload) - Generates signature for payload
  • string GenerateSignature(string secret, string payload, bool includePrefix) - Generates signature with optional prefix

Payload Processing

  • WebhookData ParseWebhookPayload(string payload) - Parse and validate webhook payload structure
  • string GetSignatureHeaderName() - Get the correct header name for webhook signatures
  • string? ExtractSignatureFromHeaders(IDictionary<string, string> headers) - Extract signature from HTTP headers

WebhookData

  • bool IsBatch - Returns true if payload contains multiple events
  • IReadOnlyList<Dictionary<string, object?>> Events - Returns list of parsed events

Error Handling

Comprehensive error handling with specific exception types:

try
{
    var response = await client.SendEmailAsync(email);
    Console.WriteLine("✓ Email sent successfully!");
    Console.WriteLine($"Response: {response}");
}
catch (ValidationException ex)
{
    // Invalid input data
    Console.WriteLine($"✗ Validation error: {ex.Message}");
    Console.WriteLine("Please check your email configuration");
}
catch (ApiException ex)
{
    // API returned an error
    Console.WriteLine($"✗ API error: {ex.Message}");
    Console.WriteLine($"  Status code: {ex.StatusCode}");
    Console.WriteLine($"  Error message: {ex.ErrorMessage}");
    Console.WriteLine("Please check your API credentials and endpoint");
}
catch (HttpException ex)
{
    // Network or HTTP-level error
    Console.WriteLine($"✗ HTTP error: {ex.Message}");
    Console.WriteLine($"  Status code: {ex.StatusCode}");
    Console.WriteLine("Please check your network connection and endpoint URL");
}
catch (Exception ex)
{
    // Other unexpected errors
    Console.WriteLine($"✗ Unexpected error: {ex.Message}");
    Console.WriteLine(ex.StackTrace);
}

Exception Types

  • ValidationException - Input validation fails
  • ApiException - API returns error response
    Properties: StatusCode, ErrorMessage
  • HttpException - HTTP communication fails
    Properties: StatusCode
  • LanefulException - Base exception class

Best Practices

  • • Always wrap API calls in try-catch
  • • Handle specific exception types first
  • • Log errors with context information
  • • Implement retry logic for transient failures

Webhook Handling

Comprehensive webhook handling with signature verification, payload parsing, and validation:

Basic Signature Verification

using Laneful.Webhooks;

// In your webhook handler
var payload = await request.Body.ReadAsStringAsync(); // Get the raw request body
var signature = request.Headers["x-webhook-signature"].FirstOrDefault();
var secret = "your-webhook-secret";

if (WebhookVerifier.VerifySignature(secret, payload, signature))
{
    // Process webhook data
    var webhookData = WebhookVerifier.ParseWebhookPayload(payload);
    // Handle webhook events
}
else
{
    // Invalid signature
    return BadRequest();
}

Advanced Webhook Processing

// Complete webhook verification and processing workflow
try
{
    // Step 1: Get raw payload
    var payload = await request.Body.ReadAsStringAsync();
    
    // Step 2: Extract signature from headers (supports multiple formats)
    var headers = request.Headers.ToDictionary(
        h => h.Key,
        h => h.Value.ToString(),
        StringComparer.OrdinalIgnoreCase
    );
    var signature = WebhookVerifier.ExtractSignatureFromHeaders(headers);
    
    // Step 3: Verify signature (supports sha256= prefix)
    if (string.IsNullOrEmpty(signature) || 
        !WebhookVerifier.VerifySignature(webhookSecret, payload, signature))
    {
        throw new SecurityException("Invalid webhook signature");
    }
    
    // Step 4: Parse and validate payload structure
    var webhookData = WebhookVerifier.ParseWebhookPayload(payload);
    
    // Step 5: Process events (handles both batch and single event formats)
    foreach (var eventData in webhookData.Events)
    {
        var eventType = eventData["event"]?.ToString();
        var email = eventData["email"]?.ToString();
        
        switch (eventType)
        {
            case "delivery":
                HandleDeliveryEvent(eventData);
                break;
            case "open":
                HandleOpenEvent(eventData);
                break;
            case "click":
                HandleClickEvent(eventData);
                break;
            case "bounce":
                HandleBounceEvent(eventData);
                break;
            case "drop":
                HandleDropEvent(eventData);
                break;
            case "spam_complaint":
                HandleSpamComplaintEvent(eventData);
                break;
            case "unsubscribe":
                HandleUnsubscribeEvent(eventData);
                break;
        }
    }
    
}
catch (ArgumentException ex)
{
    // Payload validation error
    return BadRequest(new { error = $"Invalid payload: {ex.Message}" });
}
catch (Exception ex)
{
    // Other errors
    return Unauthorized(new { error = ex.Message });
}

Batch Mode Support

var webhookData = WebhookVerifier.ParseWebhookPayload(payload);

if (webhookData.IsBatch)
{
    // Processing multiple events in batch mode
    Console.WriteLine($"Processing {webhookData.Events.Count} events in batch");
}
else
{
    // Processing single event
    Console.WriteLine("Processing single event");
}

// Process all events
foreach (var eventData in webhookData.Events)
{
    ProcessEvent(eventData);
}

Supported Webhook Events

Delivery Events

  • delivery - Email delivered successfully
  • bounce - Email bounced (hard or soft)
  • drop - Email dropped (spam, invalid, etc.)
  • spam_complaint - Recipient marked email as spam

Engagement Events

  • open - Email opened by recipient
  • click - Link clicked in email
  • unsubscribe - Recipient unsubscribed

Webhook Features

Security

  • • HMAC-SHA256 signature verification
  • • Support for sha256= prefix
  • • Constant-time comparison (timing attack protection)
  • • Multiple header format support

Validation

  • • JSON payload structure validation
  • • Required field validation
  • • Event type validation
  • • Email format validation
  • • UUID format validation for lane_id

Ready to Get Started?

Start sending emails with C# in minutes. Check out the examples and integrate with your application.