🔷 C# NuGet Package
Enterprise-grade Voice, SMS, Email & AI API for .NET applications and Azure cloud services
📦 Installation
The C# NuGet package will be available when released:
# Coming soon!
Install-Package TeamConnect.API
# Or via .NET CLI
dotnet add package TeamConnect.API
# For now, use HttpClient (built into .NET)
# No external dependencies needed!
🚀 Quick Start
Here's how you'll use the C# package once it's available:
// Future NuGet package usage
using TeamConnect.API;
using TeamConnect.API.Models;
// Initialize with your API key
var client = new TeamConnectClient("tc_live_your_api_key_here");
// Make a voice call
var callRequest = new CallRequest
{
To = "+447123456789",
Message = "Hello from Team Connect!"
};
var callResult = await client.Voice.MakeCallAsync(callRequest);
Console.WriteLine($"Call initiated: {callResult.CallId}");
// Send an SMS
var smsRequest = new SmsRequest
{
To = "+447123456789",
Message = "Your verification code is 123456"
};
var smsResult = await client.Sms.SendAsync(smsRequest);
Console.WriteLine($"SMS sent: {smsResult.MessageId}");
// Send an email
var emailRequest = new EmailRequest
{
To = "customer@example.com",
Subject = "Welcome to our service",
Html = "Welcome!
Thanks for signing up.
"
};
var emailResult = await client.Email.SendAsync(emailRequest);
Console.WriteLine($"Email sent: {emailResult.MessageId}");
// AI Chat
var chatRequest = new ChatRequest
{
Messages = new[]
{
new ChatMessage { Role = "system", Content = "You are a helpful assistant." },
new ChatMessage { Role = "user", Content = "Hello!" }
}
};
var aiResult = await client.AI.ChatAsync(chatRequest);
Console.WriteLine($"AI Response: {aiResult.Response}");
🔐 Current Implementation (REST API)
Until the package is ready, use our REST API with HttpClient:
// Basic HttpClient implementation
using System.Text;
using System.Text.Json;
public class TeamConnectClient
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private const string BaseUrl = "https://us-central1-customerservice-2156c.cloudfunctions.net";
public TeamConnectClient(string apiKey)
{
_apiKey = apiKey;
_httpClient = new HttpClient
{
BaseAddress = new Uri(BaseUrl),
Timeout = TimeSpan.FromSeconds(30)
};
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
public async Task> MakeRequestAsync(string service, string action, object data, CancellationToken cancellationToken = default)
{
var request = new
{
service,
action,
data,
api_key = _apiKey
};
var json = JsonSerializer.Serialize(request, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
var response = await _httpClient.PostAsync("/executeAPI", content, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
throw new TeamConnectException("Invalid API key", "INVALID_API_KEY");
}
if (response.StatusCode == System.Net.HttpStatusCode.PaymentRequired)
{
var errorResponse = JsonSerializer.Deserialize(responseContent);
throw new InsufficientCreditsException(errorResponse?.Error ?? "Insufficient credits");
}
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
throw new RateLimitExceededException("Rate limit exceeded");
}
if (!response.IsSuccessStatusCode)
{
var errorResponse = JsonSerializer.Deserialize(responseContent);
throw new TeamConnectException(errorResponse?.Error ?? $"HTTP {response.StatusCode}");
}
return JsonSerializer.Deserialize>(responseContent, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
}
catch (HttpRequestException ex)
{
throw new TeamConnectException($"Network error: {ex.Message}", ex);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
throw new TeamConnectException("Request timeout", ex);
}
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
// Response models
public class ApiResponse
{
public bool Success { get; set; }
public T Result { get; set; }
public string Error { get; set; }
public BillingInfo Billing { get; set; }
}
public class ApiErrorResponse
{
public string Error { get; set; }
public string Code { get; set; }
public string TopUpUrl { get; set; }
}
public class BillingInfo
{
public decimal Cost { get; set; }
public string CostFormatted { get; set; }
public string Service { get; set; }
public string Action { get; set; }
public decimal CreditsRemaining { get; set; }
public string CreditsRemainingFormatted { get; set; }
}
// Typed client with proper models
using System.ComponentModel.DataAnnotations;
// Request models
public class CallRequest
{
[Required]
public string To { get; set; }
[Required]
public string Message { get; set; }
public string From { get; set; }
public string Voice { get; set; }
public string WebhookUrl { get; set; }
}
public class SmsRequest
{
[Required]
public string To { get; set; }
[Required]
public string Message { get; set; }
public string From { get; set; }
}
public class EmailRequest
{
[Required]
public string To { get; set; }
[Required]
public string Subject { get; set; }
[Required]
public string Html { get; set; }
public string FromName { get; set; }
}
public class ChatRequest
{
[Required]
public ChatMessage[] Messages { get; set; }
public string Model { get; set; } = "gpt-4";
}
public class ChatMessage
{
[Required]
public string Role { get; set; }
[Required]
public string Content { get; set; }
}
// Response models
public class CallResponse
{
public string CallId { get; set; }
public string Status { get; set; }
public string To { get; set; }
public string From { get; set; }
public int EstimatedDuration { get; set; }
public DateTime CreatedAt { get; set; }
}
public class SmsResponse
{
public string MessageId { get; set; }
public string Status { get; set; }
public string To { get; set; }
public string From { get; set; }
public DateTime CreatedAt { get; set; }
}
public class EmailResponse
{
public string MessageId { get; set; }
public string Status { get; set; }
public string To { get; set; }
public string Subject { get; set; }
public DateTime CreatedAt { get; set; }
}
public class ChatResponse
{
public string Response { get; set; }
public string Model { get; set; }
public int TokensUsed { get; set; }
}
// Typed service methods
public static class TeamConnectClientExtensions
{
public static async Task<(CallResponse Result, BillingInfo Billing)> MakeCallAsync(
this TeamConnectClient client,
CallRequest request,
CancellationToken cancellationToken = default)
{
var response = await client.MakeRequestAsync("voice", "make_call", request, cancellationToken);
if (!response.Success)
{
throw new TeamConnectException(response.Error ?? "Call failed");
}
return (response.Result, response.Billing);
}
public static async Task<(SmsResponse Result, BillingInfo Billing)> SendSmsAsync(
this TeamConnectClient client,
SmsRequest request,
CancellationToken cancellationToken = default)
{
var response = await client.MakeRequestAsync("sms", "send", request, cancellationToken);
if (!response.Success)
{
throw new TeamConnectException(response.Error ?? "SMS failed");
}
return (response.Result, response.Billing);
}
public static async Task<(EmailResponse Result, BillingInfo Billing)> SendEmailAsync(
this TeamConnectClient client,
EmailRequest request,
CancellationToken cancellationToken = default)
{
var response = await client.MakeRequestAsync("email", "send", request, cancellationToken);
if (!response.Success)
{
throw new TeamConnectException(response.Error ?? "Email failed");
}
return (response.Result, response.Billing);
}
public static async Task<(ChatResponse Result, BillingInfo Billing)> ChatAsync(
this TeamConnectClient client,
ChatRequest request,
CancellationToken cancellationToken = default)
{
var response = await client.MakeRequestAsync("ai", "chat", request, cancellationToken);
if (!response.Success)
{
throw new TeamConnectException(response.Error ?? "AI chat failed");
}
return (response.Result, response.Billing);
}
}
// Exception classes
public class TeamConnectException : Exception
{
public string Code { get; }
public TeamConnectException(string message, string code = null) : base(message)
{
Code = code;
}
public TeamConnectException(string message, Exception innerException) : base(message, innerException)
{
}
}
public class InsufficientCreditsException : TeamConnectException
{
public InsufficientCreditsException(string message) : base(message, "INSUFFICIENT_CREDITS")
{
}
}
public class RateLimitExceededException : TeamConnectException
{
public RateLimitExceededException(string message) : base(message, "RATE_LIMIT_EXCEEDED")
{
}
}
// Usage example
var client = new TeamConnectClient("tc_live_your_api_key_here");
try
{
var callRequest = new CallRequest
{
To = "+447123456789",
Message = "Hello from C# with types!",
Voice = "Polly.Amy"
};
var (callResult, billing) = await client.MakeCallAsync(callRequest);
Console.WriteLine("✅ Call initiated successfully!");
Console.WriteLine($"Call ID: {callResult.CallId}");
Console.WriteLine($"Status: {callResult.Status}");
Console.WriteLine($"Cost: {billing.CostFormatted}");
if (billing.CreditsRemaining < 500)
{
Console.WriteLine($"⚠️ Low credits remaining: {billing.CreditsRemainingFormatted}");
}
}
catch (InsufficientCreditsException)
{
Console.WriteLine("💰 Please top up your account");
}
catch (TeamConnectException ex)
{
Console.WriteLine($"❌ Error: {ex.Message}");
}
// Dependency Injection setup for ASP.NET Core
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
// Configuration model
public class TeamConnectOptions
{
public const string SectionName = "TeamConnect";
public string ApiKey { get; set; }
public string BaseUrl { get; set; } = "https://us-central1-customerservice-2156c.cloudfunctions.net";
public int TimeoutSeconds { get; set; } = 30;
public int MaxRetries { get; set; } = 3;
}
// Service interface
public interface ITeamConnectService
{
Task<(CallResponse Result, BillingInfo Billing)> MakeCallAsync(CallRequest request, CancellationToken cancellationToken = default);
Task<(SmsResponse Result, BillingInfo Billing)> SendSmsAsync(SmsRequest request, CancellationToken cancellationToken = default);
Task<(EmailResponse Result, BillingInfo Billing)> SendEmailAsync(EmailRequest request, CancellationToken cancellationToken = default);
Task<(ChatResponse Result, BillingInfo Billing)> ChatAsync(ChatRequest request, CancellationToken cancellationToken = default);
}
// Service implementation
public class TeamConnectService : ITeamConnectService
{
private readonly HttpClient _httpClient;
private readonly TeamConnectOptions _options;
private readonly ILogger _logger;
public TeamConnectService(HttpClient httpClient, IOptions options, ILogger logger)
{
_httpClient = httpClient;
_options = options.Value;
_logger = logger;
}
public async Task<(CallResponse Result, BillingInfo Billing)> MakeCallAsync(CallRequest request, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Making voice call to {PhoneNumber}", request.To);
var response = await MakeRequestWithRetryAsync("voice", "make_call", request, cancellationToken);
_logger.LogInformation("Voice call completed with ID {CallId}", response.Result.CallId);
return (response.Result, response.Billing);
}
public async Task<(SmsResponse Result, BillingInfo Billing)> SendSmsAsync(SmsRequest request, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Sending SMS to {PhoneNumber}", request.To);
var response = await MakeRequestWithRetryAsync("sms", "send", request, cancellationToken);
_logger.LogInformation("SMS sent with ID {MessageId}", response.Result.MessageId);
return (response.Result, response.Billing);
}
public async Task<(EmailResponse Result, BillingInfo Billing)> SendEmailAsync(EmailRequest request, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Sending email to {EmailAddress}", request.To);
var response = await MakeRequestWithRetryAsync("email", "send", request, cancellationToken);
_logger.LogInformation("Email sent with ID {MessageId}", response.Result.MessageId);
return (response.Result, response.Billing);
}
public async Task<(ChatResponse Result, BillingInfo Billing)> ChatAsync(ChatRequest request, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Making AI chat request with {MessageCount} messages", request.Messages.Length);
var response = await MakeRequestWithRetryAsync("ai", "chat", request, cancellationToken);
_logger.LogInformation("AI chat completed, used {TokenCount} tokens", response.Result.TokensUsed);
return (response.Result, response.Billing);
}
private async Task> MakeRequestWithRetryAsync(string service, string action, object data, CancellationToken cancellationToken)
{
for (int attempt = 0; attempt <= _options.MaxRetries; attempt++)
{
try
{
return await MakeRequestAsync(service, action, data, cancellationToken);
}
catch (RateLimitExceededException) when (attempt < _options.MaxRetries)
{
var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
_logger.LogWarning("Rate limited, retrying in {Delay}s (attempt {Attempt})", delay.TotalSeconds, attempt + 1);
await Task.Delay(delay, cancellationToken);
}
catch (TeamConnectException ex) when (ex.Code == "NETWORK_ERROR" && attempt < _options.MaxRetries)
{
var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
_logger.LogWarning("Network error, retrying in {Delay}s (attempt {Attempt}): {Error}", delay.TotalSeconds, attempt + 1, ex.Message);
await Task.Delay(delay, cancellationToken);
}
}
throw new TeamConnectException("Request failed after all retries");
}
private async Task> MakeRequestAsync(string service, string action, object data, CancellationToken cancellationToken)
{
var request = new
{
service,
action,
data,
api_key = _options.ApiKey
};
var json = JsonSerializer.Serialize(request, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/executeAPI", content, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
// Handle different status codes
switch (response.StatusCode)
{
case System.Net.HttpStatusCode.Unauthorized:
throw new TeamConnectException("Invalid API key", "INVALID_API_KEY");
case System.Net.HttpStatusCode.PaymentRequired:
var errorResponse = JsonSerializer.Deserialize(responseContent);
throw new InsufficientCreditsException(errorResponse?.Error ?? "Insufficient credits");
case System.Net.HttpStatusCode.TooManyRequests:
throw new RateLimitExceededException("Rate limit exceeded");
case System.Net.HttpStatusCode.OK:
return JsonSerializer.Deserialize>(responseContent, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
default:
var apiError = JsonSerializer.Deserialize(responseContent);
throw new TeamConnectException(apiError?.Error ?? $"HTTP {response.StatusCode}");
}
}
}
// Service registration extension
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddTeamConnect(this IServiceCollection services, IConfiguration configuration)
{
services.Configure(configuration.GetSection(TeamConnectOptions.SectionName));
services.AddHttpClient((serviceProvider, client) =>
{
var options = serviceProvider.GetRequiredService>().Value;
client.BaseAddress = new Uri(options.BaseUrl);
client.Timeout = TimeSpan.FromSeconds(options.TimeoutSeconds);
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
return services;
}
}
// appsettings.json configuration
/*
{
"TeamConnect": {
"ApiKey": "tc_live_your_api_key_here",
"TimeoutSeconds": 30,
"MaxRetries": 3
}
}
*/
// Program.cs (ASP.NET Core)
/*
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddTeamConnect(builder.Configuration);
builder.Services.AddControllers();
var app = builder.Build();
// Configure pipeline
app.UseRouting();
app.MapControllers();
app.Run();
*/
// Usage in controller
[ApiController]
[Route("api/[controller]")]
public class CommunicationController : ControllerBase
{
private readonly ITeamConnectService _teamConnect;
private readonly ILogger _logger;
public CommunicationController(ITeamConnectService teamConnect, ILogger logger)
{
_teamConnect = teamConnect;
_logger = logger;
}
[HttpPost("call")]
public async Task MakeCall([FromBody] CallRequest request, CancellationToken cancellationToken)
{
try
{
var (result, billing) = await _teamConnect.MakeCallAsync(request, cancellationToken);
return Ok(new
{
Success = true,
Data = result,
Billing = billing
});
}
catch (InsufficientCreditsException ex)
{
return StatusCode(402, new { Success = false, Error = ex.Message });
}
catch (TeamConnectException ex)
{
_logger.LogError(ex, "Team Connect API error");
return StatusCode(500, new { Success = false, Error = ex.Message });
}
}
[HttpPost("sms")]
public async Task SendSms([FromBody] SmsRequest request, CancellationToken cancellationToken)
{
try
{
var (result, billing) = await _teamConnect.SendSmsAsync(request, cancellationToken);
return Ok(new
{
Success = true,
Data = result,
Billing = billing
});
}
catch (TeamConnectException ex)
{
_logger.LogError(ex, "Team Connect API error");
return StatusCode(500, new { Success = false, Error = ex.Message });
}
}
}
📞 Voice Calls
Make AI-powered voice calls with C#'s async/await patterns:
// Voice call service with comprehensive features
public class VoiceCallService
{
private readonly ITeamConnectService _teamConnect;
private readonly ILogger _logger;
public VoiceCallService(ITeamConnectService teamConnect, ILogger logger)
{
_teamConnect = teamConnect;
_logger = logger;
}
public async Task MakeAppointmentReminderAsync(Appointment appointment, CancellationToken cancellationToken = default)
{
var message = $"Hi {appointment.CustomerName}, this is confirming your {appointment.ServiceType} appointment tomorrow at {appointment.DateTime:h:mm tt}. Reply YES to confirm or NO to reschedule.";
var request = new CallRequest
{
To = appointment.CustomerPhone,
Message = message,
Voice = "Polly.Amy",
WebhookUrl = $"https://yourapp.com/webhooks/appointment/{appointment.Id}"
};
try
{
var (result, billing) = await _teamConnect.MakeCallAsync(request, cancellationToken);
_logger.LogInformation("Appointment reminder call initiated for {AppointmentId}: {CallId}",
appointment.Id, result.CallId);
return new CallResult
{
Success = true,
CallId = result.CallId,
Status = result.Status,
Cost = billing.Cost,
CreditsRemaining = billing.CreditsRemaining
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to make appointment reminder call for {AppointmentId}", appointment.Id);
return new CallResult
{
Success = false,
Error = ex.Message
};
}
}
public async Task MakePaymentReminderAsync(Invoice invoice, CancellationToken cancellationToken = default)
{
var message = $"Hello {invoice.CustomerName}, this is a friendly reminder that your payment of £{invoice.Amount:F2} is due. You can pay online at {invoice.PaymentUrl} or call us back.";
var request = new CallRequest
{
To = invoice.CustomerPhone,
Message = message,
WebhookUrl = $"https://yourapp.com/webhooks/payment/{invoice.Id}"
};
try
{
var (result, billing) = await _teamConnect.MakeCallAsync(request, cancellationToken);
_logger.LogInformation("Payment reminder call initiated for invoice {InvoiceId}: {CallId}",
invoice.Id, result.CallId);
return new CallResult
{
Success = true,
CallId = result.CallId,
Status = result.Status,
Cost = billing.Cost,
CreditsRemaining = billing.CreditsRemaining
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to make payment reminder call for invoice {InvoiceId}", invoice.Id);
return new CallResult
{
Success = false,
Error = ex.Message
};
}
}
public async Task> MakeBatchCallsAsync(IEnumerable requests, int maxConcurrency = 5, CancellationToken cancellationToken = default)
{
var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
var tasks = requests.Select(async request =>
{
await semaphore.WaitAsync(cancellationToken);
try
{
var (result, billing) = await _teamConnect.MakeCallAsync(request, cancellationToken);
return new CallResult
{
Success = true,
CallId = result.CallId,
Status = result.Status,
To = result.To,
Cost = billing.Cost,
CreditsRemaining = billing.CreditsRemaining
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Batch call failed for {PhoneNumber}", request.To);
return new CallResult
{
Success = false,
To = request.To,
Error = ex.Message
};
}
finally
{
semaphore.Release();
}
});
return (await Task.WhenAll(tasks)).ToList();
}
}
// Supporting models
public class Appointment
{
public string Id { get; set; }
public string CustomerName { get; set; }
public string CustomerPhone { get; set; }
public string ServiceType { get; set; }
public DateTime DateTime { get; set; }
}
public class Invoice
{
public string Id { get; set; }
public string CustomerName { get; set; }
public string CustomerPhone { get; set; }
public decimal Amount { get; set; }
public string PaymentUrl { get; set; }
}
public class CallResult
{
public bool Success { get; set; }
public string CallId { get; set; }
public string Status { get; set; }
public string To { get; set; }
public decimal Cost { get; set; }
public decimal CreditsRemaining { get; set; }
public string Error { get; set; }
}
// Usage example
public async Task ProcessAppointmentReminders()
{
var voiceService = serviceProvider.GetRequiredService();
var appointment = new Appointment
{
Id = "apt_123",
CustomerName = "John Doe",
CustomerPhone = "+447123456789",
ServiceType = "dental cleaning",
DateTime = DateTime.Tomorrow.AddHours(14) // 2 PM tomorrow
};
var result = await voiceService.MakeAppointmentReminderAsync(appointment);
if (result.Success)
{
Console.WriteLine($"✅ Appointment reminder sent: {result.CallId}");
Console.WriteLine($"💰 Cost: £{result.Cost:F2}");
if (result.CreditsRemaining < 5.00m)
{
Console.WriteLine("⚠️ Low credits remaining!");
}
}
else
{
Console.WriteLine($"❌ Failed to send reminder: {result.Error}");
}
}
💬 SMS Messages
Send SMS messages with C#'s robust async patterns:
// SMS service with 2FA and notifications
public class SmsService
{
private readonly ITeamConnectService _teamConnect;
private readonly IMemoryCache _cache;
private readonly ILogger _logger;
public SmsService(ITeamConnectService teamConnect, IMemoryCache cache, ILogger logger)
{
_teamConnect = teamConnect;
_cache = cache;
_logger = logger;
}
public async Task SendTwoFactorCodeAsync(string phoneNumber, string appName = "Dad-Link", CancellationToken cancellationToken = default)
{
// Generate 6-digit code
var code = Random.Shared.Next(100000, 999999).ToString();
// Store in cache with 5-minute expiry
var cacheKey = $"2fa:{phoneNumber}";
var cacheValue = new TwoFactorCache
{
Code = code,
Attempts = 0,
CreatedAt = DateTime.UtcNow
};
_cache.Set(cacheKey, cacheValue, TimeSpan.FromMinutes(5));
var request = new SmsRequest
{
To = phoneNumber,
Message = $"Your {appName} verification code: {code}"
};
try
{
var (result, billing) = await _teamConnect.SendSmsAsync(request, cancellationToken);
_logger.LogInformation("2FA code sent to {PhoneNumber}: {MessageId}",
phoneNumber, result.MessageId);
return new TwoFactorResult
{
Success = true,
MessageId = result.MessageId,
Code = code, // Don't expose in production
ExpiresAt = DateTime.UtcNow.AddMinutes(5)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send 2FA code to {PhoneNumber}", phoneNumber);
// Remove from cache on failure
_cache.Remove(cacheKey);
return new TwoFactorResult
{
Success = false,
Error = ex.Message
};
}
}
public bool VerifyTwoFactorCode(string phoneNumber, string inputCode)
{
var cacheKey = $"2fa:{phoneNumber}";
if (!_cache.TryGetValue(cacheKey, out TwoFactorCache cacheValue))
{
_logger.LogWarning("2FA verification failed - code not found for {PhoneNumber}", phoneNumber);
return false;
}
// Check if expired
if (DateTime.UtcNow > cacheValue.CreatedAt.AddMinutes(5))
{
_cache.Remove(cacheKey);
_logger.LogWarning("2FA verification failed - code expired for {PhoneNumber}", phoneNumber);
return false;
}
// Check attempts
if (cacheValue.Attempts >= 3)
{
_cache.Remove(cacheKey);
_logger.LogWarning("2FA verification failed - too many attempts for {PhoneNumber}", phoneNumber);
return false;
}
// Increment attempts
cacheValue.Attempts++;
_cache.Set(cacheKey, cacheValue, TimeSpan.FromMinutes(5));
// Verify code
if (cacheValue.Code == inputCode)
{
_cache.Remove(cacheKey);
_logger.LogInformation("2FA verification successful for {PhoneNumber}", phoneNumber);
return true;
}
_logger.LogWarning("2FA verification failed - invalid code for {PhoneNumber}", phoneNumber);
return false;
}
public async Task> SendOrderNotificationsAsync(IEnumerable orders, CancellationToken cancellationToken = default)
{
var semaphore = new SemaphoreSlim(10, 10); // Limit concurrent SMS
var tasks = orders.Select(async order =>
{
await semaphore.WaitAsync(cancellationToken);
try
{
var request = new SmsRequest
{
To = order.CustomerPhone,
Message = $"Your order #{order.Number} is {order.Status}. Track: {order.TrackingUrl}"
};
var (result, billing) = await _teamConnect.SendSmsAsync(request, cancellationToken);
_logger.LogInformation("Order notification sent for {OrderNumber}: {MessageId}",
order.Number, result.MessageId);
return new SmsResult
{
Success = true,
MessageId = result.MessageId,
To = result.To,
OrderNumber = order.Number,
Cost = billing.Cost
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send order notification for {OrderNumber}", order.Number);
return new SmsResult
{
Success = false,
To = order.CustomerPhone,
OrderNumber = order.Number,
Error = ex.Message
};
}
finally
{
semaphore.Release();
}
});
return (await Task.WhenAll(tasks)).ToList();
}
}
// Supporting models
public class TwoFactorCache
{
public string Code { get; set; }
public int Attempts { get; set; }
public DateTime CreatedAt { get; set; }
}
public class TwoFactorResult
{
public bool Success { get; set; }
public string MessageId { get; set; }
public string Code { get; set; } // For testing only
public DateTime ExpiresAt { get; set; }
public string Error { get; set; }
}
public class Order
{
public string Number { get; set; }
public string CustomerPhone { get; set; }
public string Status { get; set; }
public string TrackingUrl { get; set; }
}
public class SmsResult
{
public bool Success { get; set; }
public string MessageId { get; set; }
public string To { get; set; }
public string OrderNumber { get; set; }
public decimal Cost { get; set; }
public string Error { get; set; }
}
// Usage example in controller
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly SmsService _smsService;
public AuthController(SmsService smsService)
{
_smsService = smsService;
}
[HttpPost("send-2fa")]
public async Task SendTwoFactorCode([FromBody] Send2FARequest request)
{
var result = await _smsService.SendTwoFactorCodeAsync(request.PhoneNumber, "Dad-Link");
if (result.Success)
{
return Ok(new
{
Success = true,
MessageId = result.MessageId,
ExpiresAt = result.ExpiresAt
});
}
return BadRequest(new
{
Success = false,
Error = result.Error
});
}
[HttpPost("verify-2fa")]
public IActionResult VerifyTwoFactorCode([FromBody] Verify2FARequest request)
{
var isValid = _smsService.VerifyTwoFactorCode(request.PhoneNumber, request.Code);
return Ok(new
{
Success = isValid,
Message = isValid ? "Code verified successfully" : "Invalid or expired code"
});
}
}
public class Send2FARequest
{
[Required]
public string PhoneNumber { get; set; }
}
public class Verify2FARequest
{
[Required]
public string PhoneNumber { get; set; }
[Required]
public string Code { get; set; }
}
📧 Email Sending
Send HTML emails with C#'s templating and async capabilities:
// Email service with templating system
public class EmailService
{
private readonly ITeamConnectService _teamConnect;
private readonly ILogger _logger;
public EmailService(ITeamConnectService teamConnect, ILogger logger)
{
_teamConnect = teamConnect;
_logger = logger;
}
public async Task SendWelcomeEmailAsync(WelcomeEmailData data, CancellationToken cancellationToken = default)
{
var template = GetWelcomeEmailTemplate();
var html = RenderTemplate(template, data);
var request = new EmailRequest
{
To = data.CustomerEmail,
Subject = $"Welcome to {data.CompanyName} - Your Account is Ready!",
Html = html,
FromName = $"{data.CompanyName} Team"
};
return await SendEmailAsync(request, "welcome", cancellationToken);
}
public async Task SendInvoiceEmailAsync(InvoiceEmailData data, CancellationToken cancellationToken = default)
{
var template = GetInvoiceEmailTemplate();
var html = RenderTemplate(template, data);
var request = new EmailRequest
{
To = data.CustomerEmail,
Subject = $"Invoice #{data.InvoiceNumber} - Payment Due",
Html = html,
FromName = "Dad-Link Billing"
};
return await SendEmailAsync(request, "invoice", cancellationToken);
}
public async Task> SendEmailCampaignAsync(string templateName, IEnumerable recipients, Func selector, int maxConcurrency = 5, CancellationToken cancellationToken = default)
{
var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
var tasks = recipients.Select(async recipient =>
{
await semaphore.WaitAsync(cancellationToken);
try
{
var (email, subject, data) = selector(recipient);
var template = GetEmailTemplate(templateName);
var html = RenderTemplate(template, data);
var request = new EmailRequest
{
To = email,
Subject = subject,
Html = html,
FromName = "Dad-Link Team"
};
return await SendEmailAsync(request, templateName, cancellationToken);
}
finally
{
semaphore.Release();
}
});
return (await Task.WhenAll(tasks)).ToList();
}
private async Task SendEmailAsync(EmailRequest request, string templateType, CancellationToken cancellationToken)
{
try
{
var (result, billing) = await _teamConnect.SendEmailAsync(request, cancellationToken);
_logger.LogInformation("{TemplateType} email sent to {Email}: {MessageId}",
templateType, request.To, result.MessageId);
return new EmailResult
{
Success = true,
MessageId = result.MessageId,
To = result.To,
Subject = result.Subject,
Cost = billing.Cost,
CreditsRemaining = billing.CreditsRemaining
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send {TemplateType} email to {Email}", templateType, request.To);
return new EmailResult
{
Success = false,
To = request.To,
Subject = request.Subject,
Error = ex.Message
};
}
}
private string GetWelcomeEmailTemplate()
{
return @"
Welcome to {{CompanyName}}!
Hi {{CustomerName}},
Thanks for signing up! Your account is now active and ready to use.
Get started by exploring our features:
- 🎤 AI Voice Calls
- 💬 SMS Messaging
- 📧 Email Campaigns
- 🤖 AI Chat Integration
Get Started
If you have any questions, just reply to this email!
© 2025 {{CompanyName}}. All rights reserved.
You received this email because you signed up for our service.
";
}
private string GetInvoiceEmailTemplate()
{
return @"
Invoice #{{InvoiceNumber}}
Hi {{CustomerName}},
Your invoice is ready for payment.
Amount Due: £{{Amount:F2}}
Due Date: {{DueDate:MMMM d, yyyy}}
Pay Now
";
}
private string GetEmailTemplate(string templateName)
{
// In production, load from database or file system
return templateName switch
{
"welcome" => GetWelcomeEmailTemplate(),
"invoice" => GetInvoiceEmailTemplate(),
_ => throw new ArgumentException($"Unknown template: {templateName}")
};
}
private string RenderTemplate(string template, object data)
{
// Simple template rendering - in production, use a proper template engine
var rendered = template;
var properties = data.GetType().GetProperties();
foreach (var prop in properties)
{
var value = prop.GetValue(data);
var placeholder = $"{{{{{prop.Name}}}}}";
if (value is DateTime dateTime)
{
rendered = rendered.Replace(placeholder, dateTime.ToString("MMMM d, yyyy"));
}
else if (value is decimal || value is double || value is float)
{
rendered = rendered.Replace(placeholder, $"{value:F2}");
}
else
{
rendered = rendered.Replace(placeholder, value?.ToString() ?? "");
}
}
return rendered;
}
}
// Supporting models
public class WelcomeEmailData
{
public string CustomerName { get; set; }
public string CustomerEmail { get; set; }
public string CompanyName { get; set; }
public string DashboardUrl { get; set; }
}
public class InvoiceEmailData
{
public string CustomerName { get; set; }
public string CustomerEmail { get; set; }
public string InvoiceNumber { get; set; }
public decimal Amount { get; set; }
public DateTime DueDate { get; set; }
public string PaymentUrl { get; set; }
}
public class EmailResult
{
public bool Success { get; set; }
public string MessageId { get; set; }
public string To { get; set; }
public string Subject { get; set; }
public decimal Cost { get; set; }
public decimal CreditsRemaining { get; set; }
public string Error { get; set; }
}
// Usage example
public async Task ProcessWelcomeEmails()
{
var emailService = serviceProvider.GetRequiredService();
var welcomeData = new WelcomeEmailData
{
CustomerName = "John Doe",
CustomerEmail = "john@example.com",
CompanyName = "Dad-Link",
DashboardUrl = "https://team-connect.co.uk/dashboard.html"
};
var result = await emailService.SendWelcomeEmailAsync(welcomeData);
if (result.Success)
{
Console.WriteLine($"✅ Welcome email sent: {result.MessageId}");
Console.WriteLine($"💰 Cost: £{result.Cost:F4}");
}
else
{
Console.WriteLine($"❌ Failed to send welcome email: {result.Error}");
}
}
🤖 AI Chat
Use GPT-4 for intelligent conversations with C#'s powerful async patterns:
// AI Chat service with conversation management
public class AIChatService
{
private readonly ITeamConnectService _teamConnect;
private readonly IMemoryCache _cache;
private readonly ILogger _logger;
public AIChatService(ITeamConnectService teamConnect, IMemoryCache cache, ILogger logger)
{
_teamConnect = teamConnect;
_cache = cache;
_logger = logger;
}
public async Task SendMessageAsync(string userId, string message, CancellationToken cancellationToken = default)
{
// Get or create conversation
var conversation = GetOrCreateConversation(userId);
// Add user message
conversation.Messages.Add(new ChatMessage
{
Role = "user",
Content = message
});
var request = new ChatRequest
{
Messages = conversation.Messages.ToArray(),
Model = "gpt-4"
};
try
{
var (result, billing) = await _teamConnect.ChatAsync(request, cancellationToken);
// Add AI response to conversation
conversation.Messages.Add(new ChatMessage
{
Role = "assistant",
Content = result.Response
});
// Update conversation in cache
conversation.LastUsed = DateTime.UtcNow;
UpdateConversation(userId, conversation);
_logger.LogInformation("AI chat response generated for user {UserId}, tokens used: {TokensUsed}",
userId, result.TokensUsed);
return new ChatResult
{
Success = true,
Response = result.Response,
Model = result.Model,
TokensUsed = result.TokensUsed,
Cost = billing.Cost,
CreditsRemaining = billing.CreditsRemaining
};
}
catch (Exception ex)
{
_logger.LogError(ex, "AI chat failed for user {UserId}", userId);
// Remove the user message on failure
if (conversation.Messages.Count > 0 && conversation.Messages.Last().Role == "user")
{
conversation.Messages.RemoveAt(conversation.Messages.Count - 1);
UpdateConversation(userId, conversation);
}
return new ChatResult
{
Success = false,
Error = ex.Message
};
}
}
public async Task GenerateContentAsync(string prompt, string systemContext = null, CancellationToken cancellationToken = default)
{
var messages = new List();
if (!string.IsNullOrEmpty(systemContext))
{
messages.Add(new ChatMessage
{
Role = "system",
Content = systemContext
});
}
messages.Add(new ChatMessage
{
Role = "user",
Content = prompt
});
var request = new ChatRequest
{
Messages = messages.ToArray(),
Model = "gpt-4"
};
try
{
var (result, billing) = await _teamConnect.ChatAsync(request, cancellationToken);
_logger.LogInformation("Content generated, tokens used: {TokensUsed}, cost: {Cost}",
result.TokensUsed, billing.CostFormatted);
return result.Response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Content generation failed for prompt: {Prompt}", prompt);
throw;
}
}
public async Task> GenerateEmailSubjectsAsync(string product, string audience, int count = 5, CancellationToken cancellationToken = default)
{
var prompt = $"Generate {count} compelling email subject lines for {product} targeting {audience}. Make them engaging and click-worthy. Return only the subject lines, one per line.";
var systemContext = "You are a marketing copywriter specializing in email subject lines.";
var content = await GenerateContentAsync(prompt, systemContext, cancellationToken);
return content
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
.Where(line => !string.IsNullOrEmpty(line))
.Take(count)
.ToList();
}
public async Task GenerateProductDescriptionAsync(string productName, string features, CancellationToken cancellationToken = default)
{
var prompt = $"Write a compelling product description for {productName} with these features: {features}. Make it professional and persuasive.";
var systemContext = "You are a product marketing specialist who writes compelling product descriptions.";
return await GenerateContentAsync(prompt, systemContext, cancellationToken);
}
public void ClearConversation(string userId)
{
var cacheKey = $"conversation:{userId}";
_cache.Remove(cacheKey);
_logger.LogInformation("Conversation cleared for user {UserId}", userId);
}
public void ClearOldConversations()
{
// This is simplified - in production, you'd need a proper cache eviction strategy
_logger.LogInformation("Old conversations cleanup triggered");
}
private Conversation GetOrCreateConversation(string userId)
{
var cacheKey = $"conversation:{userId}";
if (_cache.TryGetValue(cacheKey, out Conversation conversation))
{
return conversation;
}
conversation = new Conversation
{
UserId = userId,
Messages = new List
{
new ChatMessage
{
Role = "system",
Content = "You are a helpful customer support assistant for Dad-Link, a communication platform. Be friendly and provide accurate information about our voice, SMS, email, and AI services."
}
},
CreatedAt = DateTime.UtcNow,
LastUsed = DateTime.UtcNow
};
UpdateConversation(userId, conversation);
return conversation;
}
private void UpdateConversation(string userId, Conversation conversation)
{
var cacheKey = $"conversation:{userId}";
var options = new MemoryCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromHours(2),
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1)
};
_cache.Set(cacheKey, conversation, options);
}
}
// Supporting models
public class Conversation
{
public string UserId { get; set; }
public List Messages { get; set; } = new();
public DateTime CreatedAt { get; set; }
public DateTime LastUsed { get; set; }
}
public class ChatResult
{
public bool Success { get; set; }
public string Response { get; set; }
public string Model { get; set; }
public int TokensUsed { get; set; }
public decimal Cost { get; set; }
public decimal CreditsRemaining { get; set; }
public string Error { get; set; }
}
// Usage in SignalR Hub for real-time chat
public class ChatHub : Hub
{
private readonly AIChatService _aiChatService;
private readonly ILogger _logger;
public ChatHub(AIChatService aiChatService, ILogger logger)
{
_aiChatService = aiChatService;
_logger = logger;
}
public async Task SendMessage(string message)
{
var userId = Context.UserIdentifier;
try
{
var result = await _aiChatService.SendMessageAsync(userId, message);
if (result.Success)
{
await Clients.Caller.SendAsync("ReceiveMessage", result.Response);
// Optionally send billing info
await Clients.Caller.SendAsync("BillingUpdate", new
{
Cost = result.Cost,
CreditsRemaining = result.CreditsRemaining,
TokensUsed = result.TokensUsed
});
}
else
{
await Clients.Caller.SendAsync("Error", result.Error);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Chat error for user {UserId}", userId);
await Clients.Caller.SendAsync("Error", "An error occurred processing your message");
}
}
public async Task ClearConversation()
{
var userId = Context.UserIdentifier;
_aiChatService.ClearConversation(userId);
await Clients.Caller.SendAsync("ConversationCleared");
}
}
// Usage in controller
[ApiController]
[Route("api/[controller]")]
public class ContentController : ControllerBase
{
private readonly AIChatService _aiChatService;
public ContentController(AIChatService aiChatService)
{
_aiChatService = aiChatService;
}
[HttpPost("email-subjects")]
public async Task GenerateEmailSubjects([FromBody] GenerateSubjectsRequest request)
{
try
{
var subjects = await _aiChatService.GenerateEmailSubjectsAsync(
request.Product,
request.Audience,
request.Count
);
return Ok(new
{
Success = true,
Subjects = subjects
});
}
catch (Exception ex)
{
return StatusCode(500, new
{
Success = false,
Error = ex.Message
});
}
}
[HttpPost("product-description")]
public async Task GenerateProductDescription([FromBody] GenerateDescriptionRequest request)
{
try
{
var description = await _aiChatService.GenerateProductDescriptionAsync(
request.ProductName,
request.Features
);
return Ok(new
{
Success = true,
Description = description
});
}
catch (Exception ex)
{
return StatusCode(500, new
{
Success = false,
Error = ex.Message
});
}
}
}
public class GenerateSubjectsRequest
{
[Required]
public string Product { get; set; }
[Required]
public string Audience { get; set; }
[Range(1, 20)]
public int Count { get; set; } = 5;
}
public class GenerateDescriptionRequest
{
[Required]
public string ProductName { get; set; }
[Required]
public string Features { get; set; }
}
⚡ Framework Examples
Ready-to-use examples for popular .NET frameworks and platforms:
// Complete ASP.NET Core Web API Integration
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
// Startup/Program.cs configuration
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddTeamConnect(builder.Configuration);
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddMemoryCache();
builder.Services.AddSignalR();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// JWT configuration
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHub("/chathub");
app.Run();
// Communication Controller with full CRUD operations
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class CommunicationController : ControllerBase
{
private readonly VoiceCallService _voiceService;
private readonly SmsService _smsService;
private readonly EmailService _emailService;
private readonly ILogger _logger;
public CommunicationController(
VoiceCallService voiceService,
SmsService smsService,
EmailService emailService,
ILogger logger)
{
_voiceService = voiceService;
_smsService = smsService;
_emailService = emailService;
_logger = logger;
}
[HttpPost("voice/call")]
public async Task MakeCall([FromBody] CallRequest request)
{
try
{
var userId = User.Identity?.Name;
_logger.LogInformation("User {UserId} making call to {PhoneNumber}", userId, request.To);
var result = await _voiceService.MakeAppointmentReminderAsync(new Appointment
{
CustomerPhone = request.To,
CustomerName = "Customer",
ServiceType = "Service",
DateTime = DateTime.Now.AddDays(1)
});
if (result.Success)
{
return Ok(new
{
Success = true,
Data = new
{
CallId = result.CallId,
Status = result.Status,
Cost = result.Cost,
CreditsRemaining = result.CreditsRemaining
}
});
}
return BadRequest(new { Success = false, Error = result.Error });
}
catch (InsufficientCreditsException ex)
{
return StatusCode(402, new { Success = false, Error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error making call");
return StatusCode(500, new { Success = false, Error = "Internal server error" });
}
}
[HttpPost("sms/send")]
public async Task SendSms([FromBody] SmsRequest request)
{
try
{
var (result, billing) = await _smsService._teamConnect.SendSmsAsync(request);
return Ok(new
{
Success = true,
Data = new
{
MessageId = result.MessageId,
Status = result.Status,
Cost = billing.Cost
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending SMS");
return StatusCode(500, new { Success = false, Error = ex.Message });
}
}
[HttpPost("email/send")]
public async Task SendEmail([FromBody] EmailRequest request)
{
try
{
var result = await _emailService.SendWelcomeEmailAsync(new WelcomeEmailData
{
CustomerEmail = request.To,
CustomerName = "Customer",
CompanyName = "Dad-Link",
DashboardUrl = "https://team-connect.co.uk/dashboard.html"
});
if (result.Success)
{
return Ok(new
{
Success = true,
Data = new
{
MessageId = result.MessageId,
Cost = result.Cost
}
});
}
return BadRequest(new { Success = false, Error = result.Error });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending email");
return StatusCode(500, new { Success = false, Error = ex.Message });
}
}
[HttpPost("webhooks/team-connect")]
[AllowAnonymous]
public async Task HandleWebhook([FromBody] WebhookPayload payload)
{
try
{
_logger.LogInformation("Webhook received: {Event}", payload.Event);
// Process webhook based on event type
switch (payload.Event)
{
case "call.completed":
await ProcessCallCompleted(payload.Data);
break;
case "sms.delivered":
await ProcessSmsDelivered(payload.Data);
break;
case "email.delivered":
await ProcessEmailDelivered(payload.Data);
break;
}
return Ok(new { Received = true });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing webhook");
return StatusCode(500);
}
}
private async Task ProcessCallCompleted(Dictionary data)
{
var callId = data["call_id"]?.ToString();
var duration = Convert.ToInt32(data["duration"]);
_logger.LogInformation("Call {CallId} completed, duration: {Duration}s", callId, duration);
// Update database, send notifications, etc.
}
private async Task ProcessSmsDelivered(Dictionary data)
{
var messageId = data["message_id"]?.ToString();
_logger.LogInformation("SMS {MessageId} delivered", messageId);
// Update delivery status in database
}
private async Task ProcessEmailDelivered(Dictionary data)
{
var messageId = data["message_id"]?.ToString();
_logger.LogInformation("Email {MessageId} delivered", messageId);
// Update email campaign statistics
}
}
public class WebhookPayload
{
public string Event { get; set; }
public Dictionary Data { get; set; }
}
// Blazor Server/WebAssembly Integration
@page "/communication"
@using Microsoft.AspNetCore.SignalR.Client
@inject ITeamConnectService TeamConnect
@inject IJSRuntime JSRuntime
@implements IAsyncDisposable
Team Connect Communication
Voice Calls
SMS Messages
@(160 - (smsRequest.Message?.Length ?? 0)) characters remaining
AI Chat Assistant
@foreach (var message in chatMessages)
{
}
{ if (e.Key == "Enter") await SendChatMessage(); })"
class="form-control" placeholder="Type your message..." disabled="@isLoading" />
@if (!string.IsNullOrEmpty(statusMessage))
{
@statusMessage
}
@code {
private CallRequest callRequest = new();
private SmsRequest smsRequest = new();
private string chatInput = "";
private bool isLoading = false;
private string statusMessage = "";
private List chatMessages = new();
private HubConnection? hubConnection;
protected override async Task OnInitializedAsync()
{
// Initialize SignalR connection for real-time chat
hubConnection = new HubConnectionBuilder()
.WithUrl("/chathub")
.Build();
hubConnection.On("ReceiveMessage", (message) =>
{
chatMessages.Add(new ChatMessage
{
Content = message,
IsUser = false,
Timestamp = DateTime.Now
});
InvokeAsync(StateHasChanged);
});
hubConnection.On("Error", (error) =>
{
statusMessage = $"❌ {error}";
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
}
private async Task MakeCall()
{
isLoading = true;
statusMessage = "";
try
{
var (result, billing) = await TeamConnect.MakeCallAsync(callRequest);
statusMessage = $"✅ Call initiated: {result.CallId} (Cost: {billing.CostFormatted})";
callRequest = new CallRequest(); // Reset form
}
catch (Exception ex)
{
statusMessage = $"❌ Call failed: {ex.Message}";
}
finally
{
isLoading = false;
}
}
private async Task SendSms()
{
isLoading = true;
statusMessage = "";
try
{
var (result, billing) = await TeamConnect.SendSmsAsync(smsRequest);
statusMessage = $"✅ SMS sent: {result.MessageId} (Cost: {billing.CostFormatted})";
smsRequest = new SmsRequest(); // Reset form
}
catch (Exception ex)
{
statusMessage = $"❌ SMS failed: {ex.Message}";
}
finally
{
isLoading = false;
}
}
private async Task SendChatMessage()
{
if (string.IsNullOrWhiteSpace(chatInput) || isLoading)
return;
isLoading = true;
// Add user message to chat
chatMessages.Add(new ChatMessage
{
Content = chatInput,
IsUser = true,
Timestamp = DateTime.Now
});
var message = chatInput;
chatInput = "";
StateHasChanged();
try
{
if (hubConnection?.State == HubConnectionState.Connected)
{
await hubConnection.SendAsync("SendMessage", message);
}
}
catch (Exception ex)
{
statusMessage = $"❌ Chat error: {ex.Message}";
}
finally
{
isLoading = false;
}
}
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
public class ChatMessage
{
public string Content { get; set; } = "";
public bool IsUser { get; set; }
public DateTime Timestamp { get; set; }
}
}
// Azure Functions Integration
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.Json;
// Startup.cs or Program.cs for Azure Functions
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddSingleton(provider =>
{
var apiKey = Environment.GetEnvironmentVariable("TEAM_CONNECT_API_KEY");
var httpClient = new HttpClient();
var logger = provider.GetRequiredService>();
var options = Microsoft.Extensions.Options.Options.Create(new TeamConnectOptions
{
ApiKey = apiKey
});
return new TeamConnectService(httpClient, options, logger);
});
})
.Build();
host.Run();
// HTTP Trigger for Voice Calls
public class VoiceCallFunction
{
private readonly ITeamConnectService _teamConnect;
private readonly ILogger _logger;
public VoiceCallFunction(ITeamConnectService teamConnect, ILogger logger)
{
_teamConnect = teamConnect;
_logger = logger;
}
[Function("MakeCall")]
public async Task MakeCall(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
try
{
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var callRequest = JsonSerializer.Deserialize(requestBody);
var (result, billing) = await _teamConnect.MakeCallAsync(callRequest);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new
{
Success = true,
Data = new
{
CallId = result.CallId,
Status = result.Status,
Cost = billing.Cost
}
});
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error making call");
var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError);
await errorResponse.WriteAsJsonAsync(new
{
Success = false,
Error = ex.Message
});
return errorResponse;
}
}
}
// Timer Trigger for Scheduled Tasks
public class ScheduledCommunicationFunction
{
private readonly ITeamConnectService _teamConnect;
private readonly ILogger _logger;
public ScheduledCommunicationFunction(ITeamConnectService teamConnect, ILogger logger)
{
_teamConnect = teamConnect;
_logger = logger;
}
[Function("SendDailyReminders")]
public async Task SendDailyReminders(
[TimerTrigger("0 0 9 * * *")] TimerInfo timer) // Daily at 9 AM
{
_logger.LogInformation("Starting daily reminder job at {Time}", DateTime.UtcNow);
try
{
// Get appointments from database (simulated)
var appointments = await GetTodaysAppointments();
var tasks = appointments.Select(async appointment =>
{
var callRequest = new CallRequest
{
To = appointment.CustomerPhone,
Message = $"Hi {appointment.CustomerName}, this is a reminder for your {appointment.ServiceType} appointment today at {appointment.DateTime:h:mm tt}.",
Voice = "Polly.Amy"
};
try
{
var (result, billing) = await _teamConnect.MakeCallAsync(callRequest);
_logger.LogInformation("Reminder call sent for appointment {AppointmentId}: {CallId}",
appointment.Id, result.CallId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send reminder for appointment {AppointmentId}", appointment.Id);
}
});
await Task.WhenAll(tasks);
_logger.LogInformation("Daily reminder job completed");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in daily reminder job");
}
}
private async Task> GetTodaysAppointments()
{
// Simulate database call
await Task.Delay(100);
return new List
{
new Appointment
{
Id = "apt_1",
CustomerName = "John Doe",
CustomerPhone = "+447123456789",
ServiceType = "dental cleaning",
DateTime = DateTime.Today.AddHours(14)
}
};
}
}
// Queue Trigger for Processing Messages
public class QueueProcessorFunction
{
private readonly ITeamConnectService _teamConnect;
private readonly ILogger _logger;
public QueueProcessorFunction(ITeamConnectService teamConnect, ILogger logger)
{
_teamConnect = teamConnect;
_logger = logger;
}
[Function("ProcessSmsQueue")]
public async Task ProcessSmsQueue(
[ServiceBusTrigger("sms-queue", Connection = "ServiceBusConnection")] string message)
{
try
{
var smsTask = JsonSerializer.Deserialize(message);
var smsRequest = new SmsRequest
{
To = smsTask.PhoneNumber,
Message = smsTask.Message
};
var (result, billing) = await _teamConnect.SendSmsAsync(smsRequest);
_logger.LogInformation("SMS sent from queue: {MessageId}, Cost: {Cost}",
result.MessageId, billing.CostFormatted);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing SMS queue message: {Message}", message);
throw; // Re-throw to trigger retry logic
}
}
}
public class SmsTask
{
public string PhoneNumber { get; set; }
public string Message { get; set; }
public string TaskId { get; set; }
}
// Worker Service for Background Processing
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
// Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTeamConnect(builder.Configuration);
builder.Services.AddHostedService();
builder.Services.AddHostedService();
var host = builder.Build();
host.Run();
// Background worker for processing communication queue
public class CommunicationWorker : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
public CommunicationWorker(
IServiceProvider serviceProvider,
ILogger logger,
IConfiguration configuration)
{
_serviceProvider = serviceProvider;
_logger = logger;
_configuration = configuration;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Communication Worker started");
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = _serviceProvider.CreateScope();
var teamConnect = scope.ServiceProvider.GetRequiredService();
// Process communication queue (simulated)
var queueItems = await GetQueueItems();
if (queueItems.Any())
{
await ProcessQueueItems(teamConnect, queueItems, stoppingToken);
}
// Wait before next iteration
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
catch (OperationCanceledException)
{
_logger.LogInformation("Communication Worker stopping");
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in Communication Worker");
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); // Wait before retry
}
}
}
private async Task> GetQueueItems()
{
// Simulate getting items from queue/database
await Task.Delay(100);
return new List
{
new CommunicationTask
{
Id = Guid.NewGuid().ToString(),
Type = "sms",
PhoneNumber = "+447123456789",
Message = "Your order has been shipped!",
ScheduledFor = DateTime.UtcNow
}
};
}
private async Task ProcessQueueItems(ITeamConnectService teamConnect, List items, CancellationToken stoppingToken)
{
var semaphore = new SemaphoreSlim(5, 5); // Process 5 items concurrently
var tasks = items.Select(async item =>
{
await semaphore.WaitAsync(stoppingToken);
try
{
await ProcessQueueItem(teamConnect, item, stoppingToken);
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
private async Task ProcessQueueItem(ITeamConnectService teamConnect, CommunicationTask item, CancellationToken stoppingToken)
{
try
{
switch (item.Type.ToLower())
{
case "sms":
var smsRequest = new SmsRequest
{
To = item.PhoneNumber,
Message = item.Message
};
var (smsResult, smsBilling) = await teamConnect.SendSmsAsync(smsRequest, stoppingToken);
_logger.LogInformation("SMS sent: {MessageId}, Cost: {Cost}",
smsResult.MessageId, smsBilling.CostFormatted);
break;
case "voice":
var callRequest = new CallRequest
{
To = item.PhoneNumber,
Message = item.Message
};
var (callResult, callBilling) = await teamConnect.MakeCallAsync(callRequest, stoppingToken);
_logger.LogInformation("Call initiated: {CallId}, Cost: {Cost}",
callResult.CallId, callBilling.CostFormatted);
break;
default:
_logger.LogWarning("Unknown communication type: {Type}", item.Type);
break;
}
// Mark item as processed (simulated)
await MarkItemAsProcessed(item.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing communication task {TaskId}", item.Id);
// Mark item for retry or dead letter
await MarkItemForRetry(item.Id);
}
}
private async Task MarkItemAsProcessed(string itemId)
{
// Update database/queue to mark item as processed
await Task.Delay(50);
_logger.LogDebug("Marked item {ItemId} as processed", itemId);
}
private async Task MarkItemForRetry(string itemId)
{
// Update database/queue to mark item for retry
await Task.Delay(50);
_logger.LogDebug("Marked item {ItemId} for retry", itemId);
}
}
// Appointment reminder worker
public class AppointmentReminderWorker : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
public AppointmentReminderWorker(
IServiceProvider serviceProvider,
ILogger logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Appointment Reminder Worker started");
while (!stoppingToken.IsCancellationRequested)
{
try
{
var now = DateTime.UtcNow;
var nextRun = now.Date.AddDays(1).AddHours(8); // Run daily at 8 AM
if (now.Hour == 8 && now.Minute < 5) // Run between 8:00-8:05 AM
{
await ProcessDailyReminders(stoppingToken);
}
// Wait until next check
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
catch (OperationCanceledException)
{
_logger.LogInformation("Appointment Reminder Worker stopping");
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in Appointment Reminder Worker");
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
private async Task ProcessDailyReminders(CancellationToken stoppingToken)
{
using var scope = _serviceProvider.CreateScope();
var teamConnect = scope.ServiceProvider.GetRequiredService();
_logger.LogInformation("Processing daily appointment reminders");
// Get today's appointments (simulated)
var appointments = await GetTodaysAppointments();
foreach (var appointment in appointments)
{
if (stoppingToken.IsCancellationRequested)
break;
try
{
var reminderTime = appointment.DateTime.AddHours(-2); // Send 2 hours before
if (DateTime.UtcNow >= reminderTime)
{
var callRequest = new CallRequest
{
To = appointment.CustomerPhone,
Message = $"Hi {appointment.CustomerName}, this is a reminder for your {appointment.ServiceType} appointment today at {appointment.DateTime:h:mm tt}. Please call if you need to reschedule.",
Voice = "Polly.Amy"
};
var (result, billing) = await teamConnect.MakeCallAsync(callRequest, stoppingToken);
_logger.LogInformation("Reminder sent for appointment {AppointmentId}: {CallId}",
appointment.Id, result.CallId);
// Mark reminder as sent
await MarkReminderSent(appointment.Id);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send reminder for appointment {AppointmentId}", appointment.Id);
}
// Small delay between calls to avoid rate limiting
await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken);
}
_logger.LogInformation("Daily appointment reminders completed");
}
private async Task> GetTodaysAppointments()
{
// Simulate database call
await Task.Delay(100);
return new List();
}
private async Task MarkReminderSent(string appointmentId)
{
// Update database
await Task.Delay(50);
}
}
public class CommunicationTask
{
public string Id { get; set; }
public string Type { get; set; } // "sms", "voice", "email"
public string PhoneNumber { get; set; }
public string Message { get; set; }
public DateTime ScheduledFor { get; set; }
public int RetryCount { get; set; }
}
⚠️ Error Handling
Production-ready error handling with C#'s exception patterns:
// Comprehensive error handling with Polly
using Polly;
using Polly.Extensions.Http;
// Enhanced client with circuit breaker and retry policies
public class EnhancedTeamConnectService : ITeamConnectService
{
private readonly HttpClient _httpClient;
private readonly TeamConnectOptions _options;
private readonly ILogger _logger;
private readonly IAsyncPolicy _retryPolicy;
private readonly IAsyncPolicy _circuitBreakerPolicy;
private readonly IAsyncPolicy _combinedPolicy;
public EnhancedTeamConnectService(
HttpClient httpClient,
IOptions options,
ILogger logger)
{
_httpClient = httpClient;
_options = options.Value;
_logger = logger;
// Retry policy with exponential backoff
_retryPolicy = Policy
.HandleResult(r => !r.IsSuccessStatusCode && r.StatusCode != System.Net.HttpStatusCode.PaymentRequired)
.Or()
.WaitAndRetryAsync(
retryCount: _options.MaxRetries,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryCount, context) =>
{
_logger.LogWarning("Retry {RetryCount} for {Operation} after {Delay}ms",
retryCount, context.OperationKey, timespan.TotalMilliseconds);
});
// Circuit breaker policy
_circuitBreakerPolicy = Policy
.HandleResult(r => !r.IsSuccessStatusCode)
.Or()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (exception, duration) =>
{
_logger.LogError("Circuit breaker opened for {Duration}s due to: {Exception}",
duration.TotalSeconds, exception.Exception?.Message ?? exception.Result?.StatusCode.ToString());
},
onReset: () =>
{
_logger.LogInformation("Circuit breaker reset");
});
// Combine policies
_combinedPolicy = Policy.WrapAsync(_retryPolicy, _circuitBreakerPolicy);
}
public async Task<(CallResponse Result, BillingInfo Billing)> MakeCallAsync(CallRequest request, CancellationToken cancellationToken = default)
{
try
{
var context = new Context($"make-call-{request.To}");
var response = await _combinedPolicy.ExecuteAsync(async (ctx) =>
{
return await MakeHttpRequestAsync("voice", "make_call", request, cancellationToken);
}, context);
var apiResponse = await ProcessHttpResponse(response, cancellationToken);
if (!apiResponse.Success)
{
throw new TeamConnectException(apiResponse.Error ?? "Call failed");
}
_logger.LogInformation("Call successful: {CallId}, Cost: {Cost}",
apiResponse.Result.CallId, apiResponse.Billing?.CostFormatted);
return (apiResponse.Result, apiResponse.Billing);
}
catch (BrokenCircuitException)
{
_logger.LogError("Circuit breaker is open for voice calls");
throw new ServiceUnavailableException("Voice call service is temporarily unavailable");
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Network error making call to {PhoneNumber}", request.To);
throw new NetworkException("Network error occurred", ex);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
_logger.LogError(ex, "Timeout making call to {PhoneNumber}", request.To);
throw new TimeoutException("Request timed out", ex);
}
catch (TeamConnectException)
{
throw; // Re-throw known exceptions
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error making call to {PhoneNumber}", request.To);
throw new TeamConnectException("An unexpected error occurred", ex);
}
}
public async Task<(SmsResponse Result, BillingInfo Billing)> SendSmsAsync(SmsRequest request, CancellationToken cancellationToken = default)
{
try
{
var context = new Context($"send-sms-{request.To}");
var response = await _combinedPolicy.ExecuteAsync(async (ctx) =>
{
return await MakeHttpRequestAsync("sms", "send", request, cancellationToken);
}, context);
var apiResponse = await ProcessHttpResponse(response, cancellationToken);
if (!apiResponse.Success)
{
throw new TeamConnectException(apiResponse.Error ?? "SMS failed");
}
return (apiResponse.Result, apiResponse.Billing);
}
catch (BrokenCircuitException)
{
throw new ServiceUnavailableException("SMS service is temporarily unavailable");
}
catch (Exception ex) when (!(ex is TeamConnectException))
{
_logger.LogError(ex, "Error sending SMS to {PhoneNumber}", request.To);
throw new TeamConnectException("Failed to send SMS", ex);
}
}
public async Task<(EmailResponse Result, BillingInfo Billing)> SendEmailAsync(EmailRequest request, CancellationToken cancellationToken = default)
{
try
{
var context = new Context($"send-email-{request.To}");
var response = await _combinedPolicy.ExecuteAsync(async (ctx) =>
{
return await MakeHttpRequestAsync("email", "send", request, cancellationToken);
}, context);
var apiResponse = await ProcessHttpResponse(response, cancellationToken);
if (!apiResponse.Success)
{
throw new TeamConnectException(apiResponse.Error ?? "Email failed");
}
return (apiResponse.Result, apiResponse.Billing);
}
catch (BrokenCircuitException)
{
throw new ServiceUnavailableException("Email service is temporarily unavailable");
}
catch (Exception ex) when (!(ex is TeamConnectException))
{
_logger.LogError(ex, "Error sending email to {EmailAddress}", request.To);
throw new TeamConnectException("Failed to send email", ex);
}
}
public async Task<(ChatResponse Result, BillingInfo Billing)> ChatAsync(ChatRequest request, CancellationToken cancellationToken = default)
{
try
{
var context = new Context("ai-chat");
var response = await _combinedPolicy.ExecuteAsync(async (ctx) =>
{
return await MakeHttpRequestAsync("ai", "chat", request, cancellationToken);
}, context);
var apiResponse = await ProcessHttpResponse(response, cancellationToken);
if (!apiResponse.Success)
{
throw new TeamConnectException(apiResponse.Error ?? "AI chat failed");
}
return (apiResponse.Result, apiResponse.Billing);
}
catch (BrokenCircuitException)
{
throw new ServiceUnavailableException("AI service is temporarily unavailable");
}
catch (Exception ex) when (!(ex is TeamConnectException))
{
_logger.LogError(ex, "Error in AI chat");
throw new TeamConnectException("AI chat failed", ex);
}
}
private async Task MakeHttpRequestAsync(string service, string action, object data, CancellationToken cancellationToken)
{
var request = new
{
service,
action,
data,
api_key = _options.ApiKey
};
var json = JsonSerializer.Serialize(request, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
var content = new StringContent(json, Encoding.UTF8, "application/json");
return await _httpClient.PostAsync("/executeAPI", content, cancellationToken);
}
private async Task> ProcessHttpResponse(HttpResponseMessage response, CancellationToken cancellationToken)
{
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
switch (response.StatusCode)
{
case System.Net.HttpStatusCode.Unauthorized:
throw new InvalidApiKeyException("Invalid or revoked API key");
case System.Net.HttpStatusCode.PaymentRequired:
var errorResponse = JsonSerializer.Deserialize(responseContent);
throw new InsufficientCreditsException(errorResponse?.Error ?? "Insufficient credits");
case System.Net.HttpStatusCode.TooManyRequests:
throw new RateLimitExceededException("Rate limit exceeded");
case System.Net.HttpStatusCode.ServiceUnavailable:
throw new ServiceUnavailableException("Service temporarily unavailable");
case System.Net.HttpStatusCode.OK:
return JsonSerializer.Deserialize>(responseContent, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
default:
var apiError = JsonSerializer.Deserialize(responseContent);
throw new TeamConnectException(apiError?.Error ?? $"HTTP {response.StatusCode}");
}
}
}
// Custom exception hierarchy
public class TeamConnectException : Exception
{
public string Code { get; }
public TeamConnectException(string message, string code = null) : base(message)
{
Code = code;
}
public TeamConnectException(string message, Exception innerException) : base(message, innerException)
{
Code = "UNKNOWN_ERROR";
}
}
public class InvalidApiKeyException : TeamConnectException
{
public InvalidApiKeyException(string message) : base(message, "INVALID_API_KEY") { }
}
public class InsufficientCreditsException : TeamConnectException
{
public InsufficientCreditsException(string message) : base(message, "INSUFFICIENT_CREDITS") { }
}
public class RateLimitExceededException : TeamConnectException
{
public RateLimitExceededException(string message) : base(message, "RATE_LIMIT_EXCEEDED") { }
}
public class ServiceUnavailableException : TeamConnectException
{
public ServiceUnavailableException(string message) : base(message, "SERVICE_UNAVAILABLE") { }
}
public class NetworkException : TeamConnectException
{
public NetworkException(string message, Exception innerException) : base(message, innerException)
{
Code = "NETWORK_ERROR";
}
}
// Global exception handler for ASP.NET Core
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger _logger;
public GlobalExceptionHandler(ILogger logger)
{
_logger = logger;
}
public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
var (statusCode, response) = exception switch
{
InvalidApiKeyException ex => (401, new { Success = false, Error = ex.Message, Code = ex.Code }),
InsufficientCreditsException ex => (402, new { Success = false, Error = ex.Message, Code = ex.Code, TopUpUrl = "https://team-connect.co.uk/api-dashboard.html#topup" }),
RateLimitExceededException ex => (429, new { Success = false, Error = ex.Message, Code = ex.Code }),
ServiceUnavailableException ex => (503, new { Success = false, Error = ex.Message, Code = ex.Code }),
TeamConnectException ex => (500, new { Success = false, Error = ex.Message, Code = ex.Code }),
_ => (500, new { Success = false, Error = "An unexpected error occurred", Code = "INTERNAL_ERROR" })
};
_logger.LogError(exception, "Handling exception: {ExceptionType}", exception.GetType().Name);
httpContext.Response.StatusCode = statusCode;
await httpContext.Response.WriteAsJsonAsync(response, cancellationToken);
return true;
}
}
// Usage example with comprehensive error handling
public class CommunicationController : ControllerBase
{
private readonly ITeamConnectService _teamConnect;
public CommunicationController(ITeamConnectService teamConnect)
{
_teamConnect = teamConnect;
}
[HttpPost("call")]
public async Task MakeCall([FromBody] CallRequest request)
{
// No try-catch needed - global exception handler will handle it
var (result, billing) = await _teamConnect.MakeCallAsync(request);
return Ok(new
{
Success = true,
Data = result,
Billing = billing
});
}
}
💰 Pricing
Enterprise-grade pricing perfect for .NET applications and Azure cloud services:
Service | Price | Unit | C# Example |
---|---|---|---|
📞 Voice Calls | 7p | per minute | await client.MakeCallAsync(request) |
💬 SMS Messages | 3p | per message | await client.SendSmsAsync(request) |
📧 Email Sending | 0.15p | per email | await client.SendEmailAsync(request) |
🤖 AI Processing | 15p | per request | await client.ChatAsync(request) |
💡 Enterprise .NET Cost Example
ASP.NET Core application with 50,000 users:
- 5,000 voice calls (2 min avg) = £700.00
- 25,000 SMS messages = £750.00
- 100,000 emails = £150.00
- 2,500 AI requests = £375.00
- Total: £1,975.00/month (vs £250k+ building in-house)
💳 Manage Credits: Visit your API Dashboard to top up credits and monitor usage across your .NET applications.
Need help? Contact us at support@team-connect.co.uk
← Back to API Dashboard | REST API Docs | Azure Examples
© 2025 Dad-Link. All rights reserved. | Last updated: 2025-08-05 15:36:45 UTC