Skip to content

Version 2: a portable HTML part, a safer sender, current SQL Server - #7

Merged
Darko-Martinovic merged 1 commit into
mainfrom
modernise
Oct 29, 2026
Merged

Darko-Martinovic merged 1 commit into
mainfrom
modernise

Conversation

@Darko-Martinovic

Copy link
Copy Markdown
Owner

Brings the repository to SQL Server 2017 and later, including 2022 and 2025. The functions and the procedure keep their names and parameters. The article's code stays available as the v1-article tag.

Two parts

  • HTML (SAFE). QueryToHtml, ConCatHtml and CustomSendMailHelp move to an assembly of their own (Setup/SetupHtml.sql). It runs on Windows and on Linux, and its HTML can go out through Database Mail.
  • Sender (UNSAFE, Windows only). CLRSendMail and the new SaveProfile are in Setup/SetupSendMail.sql. SQL Server on Linux refuses anything but SAFE assemblies, and .NET Framework's SmtpClient needs full trust for any port but 25. On Linux the script stops with a message saying so.

Security

  • Installing. Both scripts trust their assembly by hash (sp_add_trusted_assembly). There is no key file, SQLCMD mode or variables to edit.
  • Version 1's key. It was made from a key file published in the repository and given UNSAFE ASSEMBLY. The scripts drop it, with its login, once no database uses version 1.
  • Passwords.
    • Before: encrypted with a key derived from a word in the source code.
    • Now: encrypted with a certificate in the database. CLRSendMail runs as the owner, so callers need EXECUTE only.
    • Version 1's credentials are re-encrypted during the upgrade.
  • TLS. The SMTP server's certificate is checked. Version 1 accepted any certificate.
  • Attachments. Files may only come from a configuration's new AttachmentFolders. Before, any file the service account could read could be mailed out.

Fixes

  • Sending.
    • Mail is sent while the caller waits. A failure is error 50000, logged in EMAIL.MonitorLog. Version 1 sent in the background and reported nothing.
    • @replyAddress works.
    • Saved attachments hold the file. Before, they were zeros.
    • A file that can't be attached stops the message.
    • The catch blocks no longer throw when an exception has no inner exception.
  • QueryToHtml.
    • @Params is a DECLARE list. The old parser's bugs with bigint, varchar, decimal(p,s) and afternoon times are gone.
    • Queries aren't split at semicolons.
    • Values, captions and footers are escaped, and look the same in any session language.
    • The footer counts rows without the header.
    • Errors are errors, instead of HTML that gets mailed.
  • Styles. The built-in styles are written inline, for clients that drop a style sheet. ConCatHtml keeps tables of custom styles.
  • Setup.sql. The old script predated QueryToHtml and ended by enabling a DDL trigger from the author's own database. Both setup scripts are now generated from templates.

Build and tests

  • The SSDT project is replaced by SDK-style .NET Framework 4.8 projects that build with dotnet build.
  • tools/SetupScript (.NET 10) generates both setup scripts. A test fails when they are older than their sources.
  • xUnit tests (.NET 10):
    • HTML and setup: run on SQL Server 2022 and 2025 for Linux in containers, and in GitHub Actions.
    • Sender: sends to a stand-in SMTP server and covers recipients, headers, attachments, mistakes, an unreachable server, TLS, EXECUTE-only callers, password storage and the version 1 upgrade. These tests need SQL Server on Windows, so they run when SQLCLR_SENDMAIL_SERVER is set. They passed locally on a SQL Server 2025 LocalDB instance and are skipped in CI.

The README is rewritten for version 2. It covers what changed, installing and upgrading, the parameters, and where SMTP sending from SQL Server stands today: password sign-in only, Microsoft 365's change, Database Mail, and SQL Server 2025's REST endpoint procedure.

- QueryToHtml and ConCatHtml move to a SAFE assembly of their own, which runs on
  SQL Server 2017+ on Windows or Linux and pairs with Database Mail
- CLRSendMail stays an UNSAFE assembly for SQL Server on Windows
- The setup scripts trust each assembly by its hash instead of the published key,
  and remove version 1's key and login once no database uses them
- Profile passwords are encrypted with a certificate in the database instead of a
  key derived from a word in the source; EMAIL.SaveProfile manages profiles, and
  version 1's credentials are re-encrypted on upgrade
- The server certificate is checked, mail is sent while the caller waits, and
  failures are errors logged in EMAIL.MonitorLog
- Attachments come only from a configuration's AttachmentFolders and are saved
  with their content; @replyAddress works
- QueryToHtml takes @params as a DECLARE list, escapes every value, keeps
  semicolons in literals and writes the built-in styles inline
- SDK-style .NET Framework 4.8 projects; tools/SetupScript generates the setup
  scripts; xUnit tests on SQL Server 2022 and 2025 for Linux in CI, and on SQL
  Server on Windows when SQLCLR_SENDMAIL_SERVER is set
Copilot AI balanced review requested due to automatic review settings October 29, 2026 11:01
@Darko-Martinovic
Darko-Martinovic merged commit c9f03aa into main Oct 29, 2026
3 checks passed
@Darko-Martinovic
Darko-Martinovic deleted the modernise branch October 29, 2026 11:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical issues risk deleting unmigrated credentials, removing trust needed by offline databases, and allowing attachment reads outside the configured allowlist.

11 open issues
What changed in this PR

Modernizes the SQLCLR utilities for SQL Server 2017+, separating portable SAFE HTML generation from Windows-only UNSAFE SMTP sending and replacing the legacy SSDT deployment.

Changes:

  • Introduces separate HTML and sender assemblies, synchronous delivery, certificate-protected passwords, and attachment allowlists.
  • Adds generated, hash-trusted installation scripts with upgrade and cleanup handling.
  • Adds SDK-style builds, integration tests, CI, and updated documentation and examples.

This pull request requires changes. Unresolved findings affect attachment restrictions, trust cleanup, credential migration, deployment permissions, sender behavior, HTML styling, Windows tests, and recovery documentation.

File Description
tools/​SetupScript/​SourceHash.cs Computes setup-source freshness hashes.
tools/​SetupScript/​SetupScript.csproj Defines the setup generator project.
tools/​SetupScript/​Program.cs Embeds assemblies and hashes into setup scripts.
tests/​SqlClr.Tests/​xunit.runner.json Disables parallel test collections.
tests/​SqlClr.Tests/​SqlClr.Tests.csproj Configures integration-test dependencies.
tests/​SqlClr.Tests/​Sql.cs Provides SQL execution and container helpers.
tests/​SqlClr.Tests/​SmtpSink.cs Implements a local SMTP capture server.
tests/​SqlClr.Tests/​SetupTests.cs Tests installation, reinstallation, and cleanup.
tests/​SqlClr.Tests/​HtmlTests.cs Tests HTML behavior and setup-script freshness.
SqlClrSendMail/​SqlClrSendMail.csproj Defines the Windows SMTP assembly.
SqlClrSendMail/​Settings/​MailProfile.cs Loads credentials and configures SMTP clients.
SqlClrSendMail/​Settings/​MailConfiguration.cs Loads logging, storage, and attachment policies.
SqlClrSendMail/​Procedures/​UpgradeVersion1Profiles.cs Migrates legacy encrypted credentials.
SqlClrSendMail/​Procedures/​CLRSendMail.cs Implements synchronous sending and error reporting.
SqlClrSendMail/​Mail/​MessageBuilder.cs Validates attachments and constructs messages.
SqlClrSendMail/​Mail/​MailRequest.cs Defines message and attachment models.
SqlClrSendMail/​Logging/​MailLog.cs Stores logs, messages, and attachment contents.
SqlClrSendMail/​CLR/​PipeUtilities.cs Emits SQL messages and errors.
SqlClrHtml/​SqlClrHtml.csproj Defines the portable SAFE HTML assembly.
SqlClrHtml/​Html/​TableStyle.cs Handles built-in and custom styles.
SqlClrHtml/​Html/​Rotation.cs Transposes result tables.
SqlClrHtml/​Html/​HtmlDocument.cs Constructs and serializes HTML documents.
SqlClrHtml/​Functions/​QueryToHtml.cs Converts query results into styled HTML tables.
SqlClrHtml/​Functions/​CustomSendMailHelp.cs Provides routine parameter documentation.
SqlClrHtml/​Functions/​ConCatHtml.cs Combines HTML documents and stylesheets.
SqlClrHtml/​Data/​ValueFormatter.cs Formats SQL values consistently.
SqlClrHtml/​Data/​ResultTable.cs Represents result columns and textual rows.
SqlClrHtml/​Data/​DataAccess.cs Reads results through the caller’s SQL context.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Test/​Test2.sql Removes the legacy repeated-send script.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Test/​Test.sql Removes legacy manual examples.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Test/​HelpTest.sql Removes legacy help queries.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Test/​CleanUp.sql Removes the legacy cleanup script.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​SqlClrCustomSendMail.sqlproj Removes the SSDT project.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Security/​EncryptSupport.cs Removes source-key credential encryption.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Properties/​AssemblyInfo.cs Removes legacy assembly metadata.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Procedures/​CustomSendMail.cs Removes the original sender implementation.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Logging/​SaveEmail.cs Removes obsolete persistence code.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Logging/​LogEntry.cs Removes the legacy log writer.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Logging/​EMailTracker.cs Removes legacy message and attachment tracking.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​HtmlSupport/​RenderArray.cs Removes the original HTML renderer.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​HtmlSupport/​DetermineStyle.cs Removes legacy stylesheet definitions and parsing.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Functions/​QueryToHtml.cs Removes the superseded query renderer.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Functions/​CustomSendMailHelp.cs Removes the original parameter-help function.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Functions/​ConCatHtml.cs Removes the original HTML merger.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Functions/​CleanMemory.cs Removes forced garbage collection.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Extensions/​MailMessageExtensions.cs Removes superseded message helpers.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Database/​SchemaDefinition.sql Removes legacy schema and security deployment.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Database/​PreDeploy.sql Removes machine-specific key provisioning.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Database/​MakeSomeNullValues.sql Removes legacy post-deployment configuration.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​DataAccess/​DataAccess.cs Removes legacy querying and parameter parsing.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Configuration/​SysProfiles.cs Removes an obsolete profile model.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Configuration/​SysProfile.cs Removes the legacy SMTP profile wrapper.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail/​Configuration/​SysConfig.cs Removes the legacy sender configuration model.
SqlClrCustomSendMail-master/​SqlClrCustomSendMail.sln Removes the SSDT-era solution.
SqlClr.slnx Groups replacement assemblies, tooling, and tests.
Setup/​SetupSendMail.template.sql Defines sender installation, upgrades, and profile management.
Setup/​SetupHtml.template.sql Defines HTML installation and assembly trust handling.
Setup/​CleanUp.sql Removes installed objects and unused server trust.
README.md Documents deployment, migration, usage, and limitations.
global.json Selects .NET 10 and the test runner.
Examples/​Examples.sql Supplies updated usage examples.
.github/​workflows/​build.yml Builds and tests against SQL Server 2022 and 2025 containers.
.gitattributes Marks embedded-assembly setup scripts as generated.

Review effort: Balanced

Comment thread Setup/CleanUp.sql
+ QUOTENAME(name) + N'.sys.assembly_files AS f ON f.assembly_id = a.assembly_id AND f.file_id = 1'
+ N' WHERE a.name IN (N''SimpleTalk.SQLCLR.SendMail'', N''SimpleTalk.SQLCLR.Html'')' AS nvarchar(max)), N' UNION ALL ')
FROM sys.databases
WHERE state = 0 AND HAS_DBACCESS(name) = 1
+ QUOTENAME(name) + N'.sys.assembly_files AS f ON f.assembly_id = a.assembly_id AND f.file_id = 1'
+ N' WHERE a.name = N''SimpleTalk.SQLCLR.Html''' AS nvarchar(max)), N' UNION ALL ')
FROM sys.databases
WHERE state = 0 AND HAS_DBACCESS(name) = 1
Comment on lines +57 to +58
IF @drop IS NOT NULL
EXEC sys.sp_executesql @drop;
Comment on lines +325 to +327
EXEC sys.sp_executesql N'EXEC EMAIL.UpgradeVersion1Profiles;';
DROP PROCEDURE EMAIL.UpgradeVersion1Profiles;
ALTER TABLE EMAIL.Profiles DROP COLUMN UserNameV1, PasswordV1;
Comment on lines +32 to +34
var full = Path.GetFullPath(path);
if (!InFolder(full, folders))
throw new SendMailException($"The attachment '{path}' is not in the configuration's AttachmentFolders.");
Comment on lines +49 to +50
IF @drop IS NOT NULL
EXEC sys.sp_executesql @drop;
EXEC sp_rename N'EMAIL.Profiles.UserName', N'UserNameV1', N'COLUMN';
EXEC sp_rename N'EMAIL.Profiles.Password', N'PasswordV1', N'COLUMN';
ALTER TABLE EMAIL.Profiles ADD UserName nvarchar(200) NULL, Password varbinary(512) NULL;
END;
: element);
}

public static string Write(XElement html) => html.ToString(SaveOptions.DisableFormatting);
{
To = Required(mailTo, "@mailTo"),
Subject = Value(mailSubject) ?? "SQLCLR Server Message",
Body = Required(mailBody, "@mailBody"),
PipeUtilities.Print("Sent successfully!");

if (config.LoggingLevel == LoggingLevel.Maximum)
MailLog.Write(cnn, "Information", "E-mail successfully sent!", header);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants