CodeLogic.Mail 5.0.121

This package has a SemVer 2.0.0 package version: 5.0.121+4885b82.
dotnet add package CodeLogic.Mail --version 5.0.121
                    
NuGet\Install-Package CodeLogic.Mail -Version 5.0.121
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodeLogic.Mail" Version="5.0.121" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CodeLogic.Mail" Version="5.0.121" />
                    
Directory.Packages.props
<PackageReference Include="CodeLogic.Mail" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CodeLogic.Mail --version 5.0.121
                    
#r "nuget: CodeLogic.Mail, 5.0.121"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CodeLogic.Mail@5.0.121
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CodeLogic.Mail&version=5.0.121
                    
Install as a Cake Addin
#tool nuget:?package=CodeLogic.Mail&version=5.0.121
                    
Install as a Cake Tool

CodeLogic.Mail

NuGet License: MIT

SMTP sending and IMAP reading for CodeLogic 5 — with a built-in template engine, attachments, and RFC 2177 IDLE push notifications.

Compose messages with a fluent MailBuilder, send them over SMTP, render variable-substitution templates (conditionals, loops, and layouts), and read mailboxes over IMAP — including live IDLE push. Built on MailKit and MimeKit. Every fallible operation returns a CodeLogic Result / Result<T> whose Error has a stable mail.* code (see MailErrorCodes) instead of throwing on protocol, auth, or timeout errors.

Install

dotnet add package CodeLogic.Mail

Quick start

using CL.Mail;

await Libraries.LoadAsync<MailLibrary>();
await CodeLogic.ConfigureAsync();
await CodeLogic.StartAsync();

var mail = Libraries.Get<MailLibrary>();

// Compose with the fluent builder and send via SMTP.
var message = mail.CreateMessage()
    .From("noreply@example.com", "My App")
    .To("user@example.com")
    .Subject("Welcome!")
    .HtmlBody("<h1>Hello</h1><p>Welcome aboard.</p>")
    .TextBody("Hello — welcome aboard.")
    .Build();

Result<MailSendReceipt> result = await mail.Smtp.SendAsync(message);
if (result.IsFailure)
    Console.WriteLine($"Send failed ({result.Error!.Code}): {result.Error.Message}");
else
    Console.WriteLine($"Sent, Message-ID {result.Value!.MessageId}");

Features

  • SMTP sending — plain-text and/or HTML bodies, CC/BCC, attachments, custom headers, and message priority. A fresh connection is opened per send (no pooling).
  • Fluent MailBuilder — mail.CreateMessage() composes an immutable MailMessage; Build() throws if sender, recipients, subject, or a body are missing.
  • Template engine — JSON-backed templates with {{var}} / ${var} / {var} substitution (HTML-encoded in HTML bodies; {{{var}}} for trusted HTML), {{#if}}…{{#else}}…{{/if}} conditionals, {{#each}}…{{/each}} loops, and {{#section}} / layout composition. Values are inserted once: placeholders inside a value are never expanded.
  • Templated send — SendTemplatedAsync renders a template by id and sends it in one call.
  • IMAP reading — list folders, fetch and page messages, fetch by UID with attachments (decoded up to Imap.MaxAttachmentBytes, 25 MiB by default; a bigger one is returned as TooLarge with no content), fetch one attachment on demand (GetAttachmentAsync), search, move/copy/delete, and add/remove flags.
  • IMAP IDLE — RFC 2177 push notifications via StartIdleAsync and the NewMailReceived event.
  • Result-based errors — operations return CodeLogic Result / Result<T>; sends return Result<MailSendReceipt> (Message-ID + send time). Failures are Error kinds (ValidationError, NotFoundError, UnauthorizedError, TimeoutError, UnavailableError, InternalError) with a MailErrorCodes code. An unexpected exception gives a generic message (Details = exception type); the exception goes to the log. Upgrading from 4.x: see the changelog.

Configuration

Auto-generated on first run as config.mail.json. The Imap section is optional — omit it to disable IMAP entirely.

{
  "enabled": true,
  "smtp": {
    "host": "smtp.example.com",
    "port": 587,
    "username": "noreply@example.com",
    "password": "your-password-or-app-password",
    "securityMode": "StartTls",
    "timeoutSeconds": 30
  },
  "imap": {
    "host": "imap.example.com",
    "port": 993,
    "username": "inbox@example.com",
    "password": "your-password-or-app-password",
    "securityMode": "SslTls",
    "timeoutSeconds": 30,
    "enableIdle": false,
    "idleRefreshMinutes": 25,
    "maxAttachmentBytes": 26214400
  },
  "defaultFromEmail": "noreply@example.com",
  "defaultFromName": "My App",
  "templateDirectory": "templates"
}
Setting Default Description
Enabled true Master switch; when false the services aren't created and health reports disabled.
Smtp.Host / Smtp.Port smtp.example.com / 587 SMTP server and port.
Smtp.Username / Smtp.Password "" SMTP credentials (use an app password where required).
Smtp.SecurityMode StartTls None, StartTls (STARTTLS, port 587), or SslTls (implicit TLS, port 465); any other value is a config error.
Smtp.TimeoutSeconds 30 SMTP connect/operation timeout.
Smtp.AttachmentBaseDirectory null When set, attachments must be files inside this directory.
Imap null Optional; omit the whole section to disable IMAP.
Imap.SecurityMode SslTls None, StartTls, or SslTls (implicit TLS, port 993).
Imap.EnableIdle false Allow RFC 2177 IDLE push monitoring (StartIdleAsync is refused while false).
Imap.IdleRefreshMinutes 25 IDLE connection refresh interval (1–28; keep under 29).
Imap.MaxAttachmentBytes 26214400 (25 MiB) Largest attachment GetMessageAsync loads into memory; a bigger one is returned with TooLarge and no content.
Imap.MaxMessageBytes 33554432 (32 MiB) Combined decoded body and attachment bytes for one message fetch (1–134217728).
Imap.MaxBodyBytes 1048576 (1 MiB) Decoded bytes of one text or HTML body (1–16777216).
Imap.MaxAttachments 100 Attachment descriptors returned per message (1–1000).
DefaultFromEmail / DefaultFromName null Fallback sender when a message from CreateMessage() / SendTemplatedAsync omits From.
TemplateDirectory templates Template folder; relative to the library data dir unless absolute.

Changing config while running

config.mail.json applies live when changed through Libraries.UpdateConfigAsync, Libraries.ReloadConfigAsync, or the config file watcher. Only what changed is rebuilt:

  • SMTP settings: mail.Smtp is replaced. Every send opens its own connection, so sends already running finish on the old settings.
  • Template directory: mail.TemplateProvider and mail.TemplateEngine are replaced.
  • IMAP settings (added, changed or removed): mail.Imap is replaced and the old connection is closed. Re-read mail.Imap — don't keep the old instance — and call StartIdleAsync again if you were idling.
  • enabled: can be switched either way.

Other settings (default sender, ...) are read when used and need nothing rebuilt.

Documentation

Full guide: CL.Mail documentation. IMAP, IDLE, receive limits and templates: Receiving & Templates.

Requirements

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on CodeLogic.Mail:

Package Downloads
CodeLogic.WebLogic

Web application toolkit for CodeLogic 5 — routing, templates, forms, an ordered security pipeline, auth flows and realtime. No third-party dependencies beyond CodeLogic.*.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
5.0.121 83 11/10/2026
5.0.118-preview 75 11/8/2026
5.0.117-preview 77 11/7/2026
5.0.116-preview 75 11/1/2026
5.0.115-preview 130 11/1/2026
5.0.114-preview 41 10/31/2026
5.0.113-preview 46 10/31/2026
4.9.102 68 10/26/2026
4.9.98 590 10/25/2026
4.8.93 74 9/23/2026
4.8.87 77 9/13/2026
4.8.86 62 9/13/2026
4.8.85 63 9/12/2026
4.6.72 247 6/20/2026
4.6.69-preview 68 6/20/2026
4.5.2 141 5/24/2026
4.5.2-preview.68 94 6/20/2026
4.5.1 187 5/24/2026
4.5.1-preview.56 119 5/24/2026
4.4.1 136 5/24/2026
Loading failed

# CL.Mail — Changelog

All notable changes to **CodeLogic.Mail** are documented here. Versions follow
[Semantic Versioning](https://semver.org/).

## 5.0 (2026-11-10)

### Breaking

- Targets **.NET 11** (`net11.0`) and requires **CodeLogic 5.0** (`[5.0.0, 6.0.0)`). On .NET 10, stay on
 4.9.x. CodeLogic 5 makes `Result<T>` / `Result` C# 15 unions and `Error` a closed record with
 kind subtypes (`NotFoundError`, `ValidationError`, ...).
- **`MailResult`, `MailResult<T>` and the `MailError` enum are removed.** Every operation now returns
 CodeLogic's `Result` / `Result<T>` and fails with an `Error` whose `Code` is a `MailErrorCodes`
 constant:

 | Old | New |
 |---|---|
 | `Task<MailResult> SmtpService.SendAsync(...)` | `Task<Result<MailSendReceipt>>` |
 | `Task<MailResult> MailLibrary.SendTemplatedAsync(...)` | `Task<Result<MailSendReceipt>>` |
 | `MailResult` (IMAP connect/disconnect/folder/message ops, `SaveTemplateAsync`) | `Result` |
 | `MailResult<T>` (fetch, search, list, load, `RenderAsync`) | `Result<T>` |
 | `MailResult.MessageId` | `MailSendReceipt.MessageId` (via `result.Value`) |
 | `MailResult.Timestamp` | `MailSendReceipt.SentAtUtc` on sends; dropped elsewhere |
 | `result.Error` (`MailError` enum) | `result.Error?.Code` (`MailErrorCodes.*` string) or the `Error` kind |
 | `result.ErrorMessage` | `result.Error?.Message` |
 | `MailError.None` | `result.IsSuccess` / `result.Error is null` |

 `IMailTemplateProvider` and `IMailTemplateEngine` implementations must change their return types
 the same way.

 Codes and kinds (each code always has the same kind):

 | `MailErrorCodes` constant | Code | `Error` kind |
 |---|---|---|
 | `InvalidRecipient` | `mail.invalid_recipient` | `ValidationError` |
 | `InvalidSender` | `mail.invalid_sender` | `ValidationError` |
 | `InvalidSubject` | `mail.invalid_subject` | `ValidationError` |
 | `InvalidBody` (new) | `mail.invalid_body` | `ValidationError` |
 | `InvalidHeader` (new) | `mail.invalid_header` | `ValidationError` |
 | `TemplateNotFound` | `mail.template_not_found` | `NotFoundError` |
 | `InvalidTemplateId` (new) | `mail.invalid_template_id` | `ValidationError` |
 | `TemplateRenderingFailed` | `mail.template_rendering_failed` | `InternalError` |
 | `SmtpConfigInvalid` | `mail.smtp_config_invalid` | `ValidationError` |
 | `SmtpAuthenticationFailed` | `mail.smtp_authentication_failed` | `UnauthorizedError` |
 | `SmtpTimeout` | `mail.smtp_timeout` | `TimeoutError` |
 | `SmtpConnectionFailed` (new) | `mail.smtp_connection_failed` | `UnavailableError` |
 | `SmtpRejected` | `mail.smtp_rejected` | `ValidationError` (`Details` has the SMTP status) |
 | `SmtpError` | `mail.smtp_error` | `InternalError` |
 | `AttachmentNotFound` | `mail.attachment_not_found` | `NotFoundError` |
 | `InvalidAttachment` (new) | `mail.invalid_attachment` | `ValidationError` |
 | `ImapConfigInvalid` | `mail.imap_config_invalid` | `ValidationError` |
 | `ImapAuthenticationFailed` | `mail.imap_authentication_failed` | `UnauthorizedError` |
 | `ImapTimeout` | `mail.imap_timeout` | `TimeoutError` |
 | `ImapConnectionFailed` (new) | `mail.imap_connection_failed` | `UnavailableError` |
 | `ImapError` | `mail.imap_error` | `InternalError` |
 | `ImapRejected` (new) | `mail.imap_rejected` | `ValidationError` (`Details` has the IMAP response) |
 | `FolderNotFound` | `mail.folder_not_found` | `NotFoundError` |
 | `MessageNotFound` | `mail.message_not_found` | `NotFoundError` |
 | `Cancelled` (new) | `mail.cancelled` | `UnavailableError` |
 | `Unknown` | `mail.unknown` | `InternalError` |

 `SmtpRejected` is a `ValidationError` because the server refused something about this message
 (sender, recipient, size, content), not because the service is broken; `Details` carries the
 SMTP status code. Cancellation previously reported `MailError.Unknown` (or
 `TemplateRenderingFailed` while rendering) and is now `mail.cancelled`.

 Checking a send result:

 ```csharp
 // 4.x
 MailResult r = await mail.Smtp.SendAsync(message);
 if (!r.IsSuccess) log.Warn($"Mail failed ({r.Error}): {r.ErrorMessage}");
 else              log.Info($"Mail sent {r.MessageId}");

 // 5.0
 Result<MailSendReceipt> r = await mail.Smtp.SendAsync(message);
 if (r.IsFailure) log.Warn($"Mail failed ({r.Error!.Code}): {r.Error.Message}");
 else             log.Info($"Mail sent {r.Value!.MessageId}");

 // or by kind
 if (r.Error is TimeoutError or UnavailableError) { /* retry later */ }
 ```

- **`SendAsync` validates more before connecting.** Malformed sender or recipient (To/Cc/Bcc)
 addresses fail with `mail.invalid_sender` / `mail.invalid_recipient` instead of an unexpected
 exception, an empty body is `mail.invalid_body` (was `MailError.InvalidRecipient`), and an
 attachment path that does not exist fails with `mail.attachment_not_found` (was silently dropped).
- **Custom headers, the subject and the sender name are validated.** A header name that is empty, not
 printable US-ASCII, or contains a space or `:` used to make MimeKit throw, which surfaced as
 `mail.unknown` (`InternalError`); it is now `mail.invalid_header` (`ValidationError`). The same code
 rejects a header value with CR, LF or NUL, and a custom header the service writes itself (From, To, Cc,
 Bcc, Subject, MIME-Version, Importance, Content-*). A `Subject` or `FromName` containing CR or LF fails
 with `mail.invalid_subject` / `mail.invalid_sender` instead of being sent.
- An unreachable SMTP/IMAP server (`SocketException`) is now `mail.smtp_connection_failed` /
 `mail.imap_connection_failed` (`UnavailableError`) instead of `Unknown` / `ImapError`.
- A template file that deserializes to nothing is `mail.template_rendering_failed` (was
 `TemplateNotFound`).
- **Attachment paths must be fully qualified.** A relative path fails with the new code
 `mail.invalid_attachment` (`ValidationError`) instead of being resolved against the working directory.
- **More headers are reserved.** Custom headers can no longer set `Sender`, `Reply-To`, `Date`, `Message-Id`,
 `Return-Path`, `Received` or `Resent-*` (in addition to From/To/Cc/Bcc/Subject/MIME-Version/Importance/
 Content-*); use the new `MailMessage.ReplyTo` / `MailBuilder.ReplyTo(...)` for a reply address.
- **`Imap.EnableIdle` is enforced.** `StartIdleAsync` fails with `mail.imap_config_invalid` while it is `false`
 (the default); it was documented as the switch but never read.
- **IMAP failures keep their kind.** Every IMAP operation reported `mail.imap_error` (`InternalError`) for any
 exception. Now an unreachable server, dropped connection or TLS failure is `mail.imap_connection_failed`,
 bad credentials `mail.imap_authentication_failed`, a timeout `mail.imap_timeout`, a missing folder or message
 `mail.folder_not_found` / `mail.message_not_found`, cancellation `mail.cancelled`, and a `NO`/`BAD` answer the
 new `mail.imap_rejected` (`ValidationError`). A reconnect inside an operation reports the real cause instead of
 an `InvalidOperationException` wrapped in `mail.imap_error`.

### Security

- **Template IDs can no longer escape the template directory.** `FileMailTemplateProvider` combined the
 directory with the raw ID, so `../x`, `sub/x` or a rooted path could load, or overwrite on save, a JSON
 file outside `TemplateDirectory`. IDs must now be file-name-safe (ASCII letters, digits, `-`, `_`, `.`;
 starting with a letter or digit; at most 128 characters), and the resolved path is checked to sit
 directly in the template directory. Anything else fails with the new code
 `MailErrorCodes.InvalidTemplateId` (`mail.invalid_template_id`, `ValidationError`) for load, save and
 layout lookup. `FileMailTemplateProvider.IsValidTemplateId` lets callers check an ID up front.

- **Unknown `SecurityMode` values fail closed.** A value outside `None`/`StartTls`/`SslTls` (e.g. a number in
 the JSON) fell back to MailKit's `Auto` (opportunistic TLS, plaintext if the server offers no STARTTLS). It is
 now a configuration error: `MailConfig.Validate` refuses it, `SendAsync` fails with `mail.smtp_config_invalid`
 and IMAP with `mail.imap_config_invalid`.
- **`Smtp.AttachmentBaseDirectory`** (new, optional): when set, attachments outside it (after resolving `..`
 and link targets) fail with `mail.invalid_attachment`.
- **The IMAP connection is serialized.** IDLE and public calls shared one `ImapClient` without a lock, which
 MailKit does not support. Calls now take the connection one at a time; a call made while IDLE runs ends the
 IDLE command gracefully, runs, and IDLE resumes.

### Fixed

- **Unexpected errors no longer echo exception text to callers.** `mail.unknown` (SMTP catch-all, template
 listing and saving), `mail.smtp_error`, `mail.imap_error` (unexpected IMAP exceptions) and
 `mail.template_rendering_failed` (load and render failures) returned `ex.Message` as the error message and the
 exception type as `Details`. They now carry a fixed message; `Details` is the exception's type name and the
 exception itself is logged. IMAP server answers (`mail.imap_rejected`, missing folder or message names) are
 unchanged.
- **`MailMessage` tolerates `null` collections.** `Cc`, `Bcc`, `ReplyTo`, `Attachments` and `Headers` set to
 `null` (through `init` or `with`) made `SendAsync` throw `NullReferenceException` during validation; they now
 coalesce to empty, and a `null` `To` is the validation error `mail.invalid_recipient`.
- **IMAP attachments are decoded with a memory bound.** `GetMessageAsync` decoded every attachment into memory
 whatever its size. The new `Imap.MaxAttachmentBytes` (default 25 MiB) caps what is buffered: an attachment above
 it comes back with `ReceivedAttachment.TooLarge = true`, its full decoded `Size`, and `Content = null`; the
 message itself is still returned and the decoder never holds more than the limit.
- **`DefaultFromEmail` / `DefaultFromName` are used.** They were documented as the fallback sender but never
 read, so `Build()` threw without `From()`. `MailLibrary.CreateMessage()` and `SendTemplatedAsync` now return
 builders that fall back to them; the new `MailBuilder(defaultFromEmail, defaultFromName)` constructor does the
 same for builders you create. An invalid `DefaultFromEmail` (or a `DefaultFromName` with a line break) fails
 configuration validation.
- **IDLE survives a dropped connection.** After a reconnect the loop kept using the folder of the disposed
 client, so push notifications died silently after the first server drop. The folder is now resolved again on
 the new connection, and messages that arrived in between are reported.
- **`ConnectAsync` no longer leaks the previous connection**: an existing client is disconnected and disposed
 before the new one is created, and a client whose connect or login fails is disposed.
- **Live config changes are validated by the library too.** An invalid configuration that reaches
 `OnConfigChangedAsync` is logged and ignored; later changes are compared with the configuration running.

### Added

- `MailMessage.ReplyTo` and `MailBuilder.ReplyTo(email)`.
- `MailBuilder(string? defaultFromEmail, string? defaultFromName = null)`.
- `SmtpConfig.AttachmentBaseDirectory`.
- `MailErrorCodes.InvalidAttachment` (`mail.invalid_attachment`) and `MailErrorCodes.ImapRejected` (`mail.imap_rejected`).
- `ImapConfig.MaxAttachmentBytes` (default `ImapConfig.DefaultMaxAttachmentBytes`, 25 MiB; must be at least 1) and
 `ReceivedAttachment.TooLarge`.

### Documentation

- Configuration examples in the README and the docs use the camelCase keys the CodeLogic loader reads.
 The PascalCase keys shown before are matched case-sensitively and were ignored, leaving defaults active.
- The live config section no longer says "CodeLogic 4.9+". The README lists `Imap.MaxMessageBytes`,
 `MaxBodyBytes` and `MaxAttachments`, and `GetAttachmentAsync`. The docs list `GetAttachmentAsync` and
 `ReceivedMessage.ContentTruncated`, show `CreatedAt` as `DateTime`, say `MailPriority` sets only the
 `Importance` header, and switch on the result union in the error example. The README's trailing relative
 link, which broke on nuget.org, is replaced by a link to the Receiving & Templates page.
- The `Enabled` setting's description no longer mentions inbox polling (there is none) or no-op operations:
 when it is off, the `Smtp` / `Imap` accessors throw. The `InvalidHeader` code lists every reserved header.

### Changed

- Fetch bounded MIME sections instead of whole messages, cap aggregate decoded message bytes/body bytes/attachment counts, and add GetAttachmentAsync for on-demand content. Return ContentTruncated and part identifiers on descriptors.

- Package versions are pinned (MailKit and MimeKit 4.18.2) instead of floating (`4.*`), so a build restores the same dependencies every time.
- `SimpleTemplateEngine` uses source-generated regexes (`[GeneratedRegex]`) for its block, section and
 placeholder patterns instead of parsing them through the static `Regex` cache on each render. Output is
 unchanged.
- `ImapService` is split into partial files by concern (connection and error mapping, folders, messages, search,
 IDLE). No behaviour change.

## 2026-10-27

### Security

- **HTML bodies encode substituted values.** `SimpleTemplateEngine` inserted every value into
 `HtmlBody` as-is, so a user-supplied value (a user name, a browser description) could inject
 markup or links into an e-mail. In `HtmlBody` and HTML layouts, `{{var}}`, `${var}` and `{var}`
 are now HTML-encoded. Subjects and text bodies are unchanged.
- **Values are inserted once.** Substitution ran the three placeholder syntaxes one after another,
 and loop output was substituted again by the outer pass, so a value containing `${other}` or
 `{other}` was expanded: user text could pull other variables (links, tokens) into the message.
 All syntaxes are now replaced in a single pass and nothing inside a value is expanded.

### Added

- **`{{{var}}}`** inserts a value without encoding, for values that are trusted HTML.

### Fixed

- **Text layouts get the text sections.** With a layout, the text layout was rendered with the
 child's *HTML* sections (including `body`). Text and HTML layouts now each receive their own
 sections, and a child's section wins over a variable of the same name.

### Upgrade notes

- Templates that pass ready-made HTML in a variable must switch that placeholder to `{{{var}}}`.

## 2026-10-24

### Added

- **Live config changes** (requires CodeLogic 4.9+). `MailLibrary` implements `IConfigReloadable`: a
 change to `config.mail.json` made through `Libraries.UpdateConfigAsync`, `Libraries.ReloadConfigAsync`
 or the config file watcher is applied without a restart, rebuilding only what changed. Changed SMTP
 settings replace `Smtp` (sends in flight finish on the old settings); a changed template directory
 replaces `TemplateProvider`/`TemplateEngine`; changed IMAP settings replace `Imap` and close the old
 connection (re-read `Imap` and restart IDLE). `Enabled` can be switched either way.

## 2026-09-12

### Changed

- Unified the version line with the CodeLogic framework on **4.8.x**. Every official
 library and the framework now share one `major.minor`, so a given `4.8.<patch>`
 means the same generation across all packages.
- `version.txt` moved from `4.6` to `4.8`. The patch component remains the CI run
 number, composed at pack time; `AssemblyVersion` stays pinned at `Major.Minor.0.0`
 (now `4.8.0.0`) so every patch in the line loads interchangeably.

## 2026-06-20

### Documentation

- Full rewrite of the README and the multi-page docs set to the CodeLogic.Libs
 house style. The README is now concise, Markdown-only, and NuGet-safe; the
 guide is split into **Overview & Sending** (`docs/libs/mail/index.md`) and
 **Receiving & Templates** (`docs/libs/mail/receiving-templates.md`), with the
 old `docs/libs/mail.md` reduced to a redirect stub and the docs navigation
 updated accordingly. No API changes.

## [4.5.1] — 2026-06-20

### Documentation

- Rewrote the README and the Mail & Templates guide to match the current public
 API. The previous docs described members that no longer exist (`EmailMessage`,
 `EmailAttachment`, `ReplyTo`, `RenderTemplateAsync`, `ReadInboxAsync`,
 `ImapFilter`, `MarkAsReadAsync`, the `SendAsync(from:, to:, …)` /
 `SendTemplateAsync` overloads, and the `UseSsl` / `FromAddress` config keys).
- Documented the real surface: the fluent `MailBuilder` (`CreateMessage()`),
 `SmtpService.SendAsync(MailMessage)`, `MailLibrary.SendTemplatedAsync`, the
 `MailResult` / `MailError` result model, and the JSON-backed template system
 (`MailTemplate`, `IMailTemplateProvider`, `IMailTemplateEngine`).
- Documented previously undocumented features: template conditionals
 (`{{#if}}`/`{{#else}}`), loops (`{{#each}}`), sections and layouts; the
 `${var}` / `{var}` placeholder syntaxes; the full IMAP API (fetch/page, fetch
 by UID, search via `ImapSearchCriteria`, move/copy/delete, flag operations,
 folder management); and RFC 2177 IMAP IDLE push (`StartIdleAsync` /
 `NewMailReceived`) with its `EnableIdle` / `IdleRefreshMinutes` config keys.
- Corrected the documented config schema and `SecurityMode` values
 (`None` / `StartTls` / `SslTls`).

## [4.5.0] — 2026-05-24

### Changed

- **Unified versioning.** All CodeLogic.Libs now share a single version line
 controlled by `version.txt` in the repo root. This is a version alignment
 release — no functional changes to this library.
## [4.0.4] — 2026-04-16

### Changed

- README + manifest refresh for the v4 baseline. No functional changes vs 4.0.3.
- `LibraryManifest.Version` now reads from assembly metadata.

## [4.0.2] — 2026-04-09

### Changed

- Annotated mail configuration with `[ConfigField]` for the admin UI surface.
- Aligned with the v4 baseline across all libraries.

### Fixed

- Resolved null-reference warnings in `ImapService`.

## [4.0.0] — 2026-04-09

Major rewrite. Republished as v4.0.0 to reset the version line under the
unified v4 baseline. SMTP / IMAP services + a CodeLogic-native template
provider.

### Notes

- Earlier history is retained in the
 [git log](https://github.com/zyntal-com/CodeLogic.Libs/commits/main/CL.Mail).