![]() |
VOOZH | about |
dotnet add package Altinn.Dd.Correspondence --version 1.0.1
NuGet\Install-Package Altinn.Dd.Correspondence -Version 1.0.1
<PackageReference Include="Altinn.Dd.Correspondence" Version="1.0.1" />
<PackageVersion Include="Altinn.Dd.Correspondence" Version="1.0.1" />Directory.Packages.props
<PackageReference Include="Altinn.Dd.Correspondence" />Project file
paket add Altinn.Dd.Correspondence --version 1.0.1
#r "nuget: Altinn.Dd.Correspondence, 1.0.1"
#:package Altinn.Dd.Correspondence@1.0.1
#addin nuget:?package=Altinn.Dd.Correspondence&version=1.0.1Install as a Cake Addin
#tool nuget:?package=Altinn.Dd.Correspondence&version=1.0.1Install as a Cake Tool
A .NET library for sending correspondence through Altinn 3 Correspondence API. This library follows Altinn 3 patterns for HttpClient registration with Maskinporten authentication.
dotnet add package Altinn.Dd.Correspondence
dotnet add package Altinn.ApiClients.Maskinporten
Note: Altinn.ApiClients.Maskinporten is a peer dependency and must be installed separately.
Add to your appsettings.json:
{
"DdConfig": {
"MaskinportenSettings": {
"ClientId": "your-client-id",
"Environment": "test",
"EncodedJwk": "your-base64-encoded-jwk"
},
"CorrespondenceSettings": {
"CorrespondenceSettings": "your-resource-id,your-sender-org",
"UseAltinnTestServers": true,
"CountryCode": "0192"
}
}
}
Configuration Details:
ClientId: Your Maskinporten client ID (required)Environment: Either "test" or "prod" for Maskinporten environmentEncodedJwk: Base64-encoded JWK from Azure Key VaultEnableDebugLogging: Optional flag to emit verbose Maskinporten diagnostics (only enable temporarily)CorrespondenceSettings: Format is "resourceId,senderOrg"UseAltinnTestServers: Set to true for TT02 test environment, false for productionCountryCode: Country code for organization numbers (default: "0192" for Norway)Note: The scope "altinn:serviceowner altinn:correspondence.write" is hardcoded in the library - you don't need to specify it.
In your Program.cs or Startup.cs:
using Altinn.ApiClients.Maskinporten.Config;
using Altinn.Dd.Correspondence.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
// In ConfigureServices or builder.Services
services.AddDdMessagingService<SettingsJwkClientDefinition>(
configuration.GetSection("DdConfig:MaskinportenSettings"),
configuration.GetSection("DdConfig:CorrespondenceSettings"));
AddDdMessagingServiceenforces the required correspondence scope (altinn:serviceowner altinn:correspondence.write) and wires up a Maskinporten-enabledHttpClientwith Polly-based retries. Consumers only need to supply environment-specific credentials. SetEnableDebugLoggingin configuration when troubleshooting.
Inject IDdMessagingService into your classes:
public class MyService
{
private readonly IDdMessagingService _messagingService;
public MyService(IDdMessagingService messagingService)
{
_messagingService = messagingService;
}
public async Task SendCorrespondenceAsync()
{
var messageDetails = new DdMessageDetails
{
Recipient = "123456789", // Organization number
Title = "Important Notice",
Summary = "A brief summary",
Body = "The full message body",
Sender = "Your Organization",
Notification = new NotificationDetails
{
EmailSubject = "New message in Altinn",
EmailBody = "You have received a new message.",
SmsText = "New message in Altinn. Log in to read."
}
};
var receipt = await _messagingService.SendMessage(messageDetails);
}
}
What the library does for you:
Altinn.ApiClients.MaskinportenSee the SendDdCorrespondence project in this repository for a complete working example.
public class DdMessageDetails
{
public string? Recipient { get; set; } // Organization number (9 digits)
public string? Title { get; set; } // Message title
public string? Summary { get; set; } // Brief summary (supports markdown)
public string? Body { get; set; } // Full message body (supports markdown)
public string? Sender { get; set; } // Sender name
public DateTime? VisibleDateTime { get; set; } // When message becomes visible
public DateTime? ShipmentDatetime { get; set; } // When to send notifications
public bool AllowForwarding { get; set; } // Allow recipient to forward
public Guid? IdempotencyKey { get; set; } // Optional: auto-generated if null
public NotificationDetails? Notification { get; set; } // Email/SMS notifications
}
public class NotificationDetails
{
public string? EmailSubject { get; set; } // Email notification subject
public string? EmailBody { get; set; } // Email notification body
public string? SmsText { get; set; } // SMS notification text
}
try
{
var receipt = await messagingService.SendMessage(messageDetails);
}
catch (CorrespondenceServiceException ex)
{
// Handle API errors
_logger.LogError(ex, "Failed to send correspondence");
}
The service automatically retries failed requests with exponential backoff to handle transient network and API errors.
Retry Configuration:
HttpRequestException, TaskCanceledException, SocketExceptionRetry Behavior:
If you're migrating from the old AddDdCorrespondence pattern with IAccessTokenProvider:
Before:
builder.Host.AddDdCorrespondence(settings, accessTokenProvider);
After:
services.AddDdMessagingService<SettingsJwkClientDefinition>(
configuration.GetSection("DdConfig:MaskinportenSettings"),
configuration.GetSection("DdConfig:CorrespondenceSettings"));
The new pattern:
IAccessTokenProvider implementationsEnableDebugLogging are required from appsettings)This repository includes a working example project called SendDdCorrespondence that demonstrates:
IAccessTokenProvider using MaskinportenAddDdCorrespondence() extension methodKey features of the example:
Altinn.ApiClients.Maskinporten for authenticationMaskinportenTokenAdapter as a reference implementationTo use the example:
SendDdCorrespondence projectappsettings.json with your Maskinporten settingsdotnet run to test correspondence sendingThis example serves as both a testing tool and a reference implementation for integrating the Altinn.Dd.Correspondence package into your applications.
This guide covers migrating from Altinn.Oed.Messaging to Altinn.Dd.Correspondence (Altinn 3). The new package keeps the same high-level service contract while simplifying the receipt model.
Altinn.Dd.Correspondence v1.0.0 (supports net8.0)
<PackageReference Include="Altinn.Dd.Correspondence" Version="1.0.0" />
using Altinn.Dd.Correspondence.Extensions;
using Altinn.Dd.Correspondence.Models;
using Altinn.Dd.Correspondence.Models.Interfaces;
using Altinn.Dd.Correspondence.Services.Interfaces;
using Altinn.Dd.Correspondence.ExternalServices.Correspondence;
using Altinn.Dd.Correspondence.Authentication;
Remove old registration:
// OLD - Remove this
services.AddSingleton<IOedMessagingService, OedMessagingService>();
services.AddTransient<BearerTokenHandler>();
services.AddHttpClient<IOedMessagingService, OedMessagingService>()
.AddHttpMessageHandler<BearerTokenHandler>();
Replace with new registration:
// NEW - Add this
var settings = builder.Configuration.GetSection("AltinnMessagingSettings").Get<Settings>()!;
// Register your token provider (implement IAccessTokenProvider)
builder.Services.AddSingleton<IAccessTokenProvider>(sp =>
{
var maskinportenService = sp.GetRequiredService<IMaskinportenService>();
var logger = sp.GetRequiredService<ILogger<MaskinportenTokenAdapter>>();
return new MaskinportenTokenAdapter(maskinportenService, builder.Configuration, logger);
});
// Register correspondence services
builder.Host.AddDdCorrespondence(settings, builder.Services.BuildServiceProvider().GetRequiredService<IAccessTokenProvider>());
Old format:
{
"AltinnMessagingSettings": {
"BaseUrl": "https://your-altinn3-url",
"CorrespondenceSettings": {
"Sender": "Your Sender Name"
}
}
}
New format:
{
"AltinnMessagingSettings": {
"CorrespondenceSettings": "your-resource-id,your-sender",
"UseAltinnTestServers": true,
"CountryCode": "0192"
},
"DdConfig": {
"MaskinportenSettings": {
"ClientId": "your-client-id",
"Scope": "altinn:serviceowner/correspondence",
"WellKnownEndpoint": "https://maskinporten.no/.well-known/oauth-authorization-server"
}
}
}
Update all files in your project that reference the old messaging package:
Replace old namespaces:
// OLD
using Altinn.Oed.Messaging.Models;
using Altinn.Oed.Messaging.Services.Interfaces;
using Altinn.Oed.Messaging.ExternalServices.Correspondence;
// NEW
using Altinn.Dd.Correspondence.Models;
using Altinn.Dd.Correspondence.Services.Interfaces;
using Altinn.Dd.Correspondence.ExternalServices.Correspondence;
Common files to update:
IDdMessagingServiceDdMessageDetails or ReceiptExternalThe new ReceiptExternal has a simplified structure. Update all code that uses the old properties:
Old ReceiptExternal Structure:
public class ReceiptExternal
{
public ReceiptStatusEnum ReceiptStatusCode { get; set; }
public string ReceiptText { get; set; }
public ReferenceList References { get; set; } // REMOVED
public List<ReceiptExternal> SubReceipts { get; set; } // REMOVED
}
New ReceiptExternal Structure:
public class ReceiptExternal
{
public ReceiptStatusEnum ReceiptStatusCode { get; set; }
public string ReceiptText { get; set; }
}
Update ReceiptExternalExtensions.cs:
// OLD - Remove this logic
if (receipt.References.IsNullOrEmpty())
{
logger.LogCritical("Message receipt references is null or empty.");
throw new Exception($"Message receipt references is null or empty.");
}
logger.LogInformation("Message sent successfully with {ReceiptReference}",
receipt.References.FirstOrDefault()?.ReferenceValue);
// NEW - Replace with simplified logic
if (receipt is not { ReceiptStatusCode: ReceiptStatusEnum.OK })
{
logger.LogError("Unable to send message. ReceiptStatusCode: {ReceiptStatusCode}, Message: {ReceiptText}",
receipt.ReceiptStatusCode.ToString(), receipt.ReceiptText);
throw new Exception($"Unable to send message. ReceiptStatusCode: {receipt.ReceiptStatusCode.ToString()}, Message: {receipt.ReceiptText}");
}
logger.LogInformation("Message sent successfully. Message: {ReceiptText}",
receipt.ReceiptText);
Update CorrespondenceReceiptValidator.cs:
// OLD - Remove this logic
if (receipt.References.IsNullOrEmpty())
{
logger.LogCritical("Message receipt references is null or empty.");
throw new Exception($"Message receipt references is null or empty.");
}
logger.LogInformation("Message sent successfully with {ReceiptReference}",
receipt.References.FirstOrDefault()?.ReferenceValue);
// NEW - Replace with simplified logic
if (receipt is not { ReceiptStatusCode: ReceiptStatusEnum.OK })
{
logger.LogError("Unable to send message. ReceiptStatusCode: {ReceiptStatusCode}, Message: {ReceiptText}",
receipt.ReceiptStatusCode.ToString(), receipt.ReceiptText);
throw new Exception($"Unable to send message. ReceiptStatusCode: {receipt.ReceiptStatusCode.ToString()}, Message: {receipt.ReceiptText}");
}
logger.LogInformation("Message sent successfully. Message: {ReceiptText}",
receipt.ReceiptText);
Update Test Utilities:
// OLD
using Altinn.Oed.Messaging.Models;
// NEW
using Altinn.Dd.Correspondence.Models;
using Altinn.Dd.Correspondence.ExternalServices.Correspondence;
Update GetReceipt Method:
// OLD
public static ReceiptExternal GetReceipt()
=> new()
{
References = new ReferenceList
{
new Reference()
},
ReceiptStatusCode = ReceiptStatusEnum.OK
};
// NEW
public static ReceiptExternal GetReceipt()
=> new()
{
ReceiptStatusCode = ReceiptStatusEnum.OK,
ReceiptText = "Message sent successfully"
};
Update Test Helpers:
// OLD
itemService.SendMessage(Arg.Any<DdMessageDetails>(), Arg.Any<string>())
.Returns(new ReceiptExternal
{
References = new ReferenceList
{
new Reference()
},
ReceiptStatusCode = ReceiptStatusEnum.OK,
});
// NEW
itemService.SendMessage(Arg.Any<DdMessageDetails>(), Arg.Any<string>())
.Returns(new ReceiptExternal
{
ReceiptStatusCode = ReceiptStatusEnum.OK,
ReceiptText = "Message sent successfully"
});
Update Test Files: Update all test files that reference the old messaging package:
// OLD
using Altinn.Oed.Messaging.Models;
using Altinn.Oed.Messaging.Services.Interfaces;
// NEW
using Altinn.Dd.Correspondence.Models;
using Altinn.Dd.Correspondence.Services.Interfaces;
using Altinn.Dd.Correspondence.ExternalServices.Correspondence;
Update test mocks and helpers:
// OLD
itemService.SendMessage(Arg.Any<DdMessageDetails>(), Arg.Any<string>())
.Returns(new ReceiptExternal
{
References = new ReferenceList
{
new Reference()
},
ReceiptStatusCode = ReceiptStatusEnum.OK,
});
// NEW
itemService.SendMessage(Arg.Any<DdMessageDetails>(), Arg.Any<string>())
.Returns(new ReceiptExternal
{
ReceiptStatusCode = ReceiptStatusEnum.OK,
ReceiptText = "Message sent successfully"
});
Fix ProblemDetails Ambiguity:
If you have tests using ProblemDetails, you may need to fully qualify the type:
// OLD
Assert.IsType<ProblemDetails>(objectActual.Value);
var problemDetailsActual = (ProblemDetails)objectActual.Value;
// NEW
Assert.IsType<Microsoft.AspNetCore.Mvc.ProblemDetails>(objectActual.Value);
var problemDetailsActual = (Microsoft.AspNetCore.Mvc.ProblemDetails)objectActual.Value;
ReceiptExternal): add using Altinn.Dd.Correspondence.ExternalServices.Correspondence;builder.Host.AddOedCorrespondence(...) is called and Maskinporten adapter is registered as IAccessTokenProvider.MaskinportenTokenAdapter example.IDdMessagingService: Main service for sending correspondenceIAccessTokenProvider: Authentication token providerDdMessageDetails: Complete correspondence information (includes optional IdempotencyKey property)NotificationDetails: Optional email/SMS notification settingsSettings: Service configurationReceiptExternal: Response from correspondence serviceAddDdCorrespondence(): Registers all correspondence services with HttpClient, authentication, and retry policiesThis package uses an automated GitHub Actions workflow for building and publishing NuGet packages.
The workflow (release-correspondence.yaml) triggers on:
correspondence-v (e.g., correspondence-v1.0.0)correspondence-v1.0.0 to publish to NuGet.orgTo publish a new version to NuGet.org:
# Create and push a tag
git tag correspondence-v1.0.0
git push origin correspondence-v1.0.0
The workflow will automatically:
NUGET_ORG_API_KEY secret)net8.0.snupkg files for debuggingSame as parent repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.1.2-alpha | 432 | 2/18/2026 |
| 2.1.1-alpha | 109 | 2/18/2026 |
| 2.0.10-alpha | 591 | 2/9/2026 |
| 2.0.9-alpha | 111 | 2/6/2026 |
| 2.0.5-alpha | 406 | 2/5/2026 |
| 2.0.4-alpha | 110 | 2/5/2026 |
| 2.0.3-alpha | 113 | 2/5/2026 |
| 2.0.2-alpha | 146 | 1/29/2026 |
| 2.0.0-alpha | 120 | 1/26/2026 |
| 1.0.1 | 479 | 11/13/2025 |
| 1.0.0 | 1,001 | 10/29/2025 |