// .NET 8+ console example. Save as Program.cs in a dotnet new console project. // Running this sends one chargeable MMS to your explicitly configured recipient. using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using System.Text.RegularExpressions; static string Required(string name) => Environment.GetEnvironmentVariable(name) is { Length: > 0 } value ? value : throw new InvalidOperationException($"Set {name}."); var baseUri = new Uri(Required("MMS_EXPRESS_BASE_URL")); if (baseUri.Scheme != "https" || baseUri.UserInfo.Length != 0 || baseUri.AbsolutePath != "/" || baseUri.Query.Length != 0 || baseUri.Fragment.Length != 0) throw new InvalidOperationException("Use the approved HTTPS API origin, without /api/v1."); var recipient = Required("MMS_EXPRESS_TO"); if (!Regex.IsMatch(recipient, @"^\+27\d{9}$")) throw new InvalidOperationException("Use your authorised +27 recipient."); var mediaUri = new Uri(Required("MMS_EXPRESS_MEDIA_URL")); if (mediaUri.Scheme != "https" || mediaUri.UserInfo.Length != 0) throw new InvalidOperationException("Use an approved HTTPS image URL without embedded credentials."); var idempotencyKey = Required("MMS_EXPRESS_IDEMPOTENCY_KEY"); if (string.IsNullOrWhiteSpace(idempotencyKey) || idempotencyKey.Length > 128 || idempotencyKey.Any(char.IsControl)) throw new InvalidOperationException("Invalid idempotency key."); using var handler = new HttpClientHandler { AllowAutoRedirect = false }; using var client = new HttpClient(handler) { BaseAddress = baseUri, Timeout = TimeSpan.FromSeconds(60) }; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Required("MMS_EXPRESS_API_KEY")); using var submission = new HttpRequestMessage(HttpMethod.Post, "/api/v1/messages") { Content = JsonContent.Create(new { to = recipient, text = "Your September statement is attached.", media = new[] { new { url = mediaUri.AbsoluteUri } } }) }; submission.Headers.Add("Idempotency-Key", idempotencyKey); // No automatic retry. A timeout can mean an unknown submission outcome; // recover using the SAME persisted key and exact body, including media URL. using var response = await client.SendAsync(submission); if (response.StatusCode != HttpStatusCode.Accepted) throw new HttpRequestException($"Submission HTTP {(int)response.StatusCode}. Review securely before retrying."); using var accepted = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); var messageId = accepted.RootElement.GetProperty("id").GetString()!; if (!Regex.IsMatch(messageId, "^msg_[0-9a-fA-F]{32}$")) throw new InvalidOperationException("Unexpected message ID. Reconcile using the same request key."); // Persist messageId in your application's durable business-request record here. using var statusResponse = await client.GetAsync($"/api/v1/messages/{messageId}"); if (statusResponse.StatusCode != HttpStatusCode.OK) throw new HttpRequestException($"Status HTTP {(int)statusResponse.StatusCode}; keep accepted ID {messageId}."); using var status = await JsonDocument.ParseAsync(await statusResponse.Content.ReadAsStreamAsync()); Console.WriteLine(JsonSerializer.Serialize(new { id = messageId, status = status.RootElement.GetProperty("status").GetString() })); // A sent status is not delivery confirmation or a read receipt.