Rob Kraft's Software Development Blog

Software Development Insights

Still Skeptical of AI Coding? Here’s One Real Feature, Start to Finish”

Posted by robkraft on September 13, 2026

I just implemented some new features on a website I maintain, and I think it’s a great illustration of why so many software developers are excited and empowered by AI.

My website, techkc.org, has a calendar that shows a small grid for the month along with a list of all the events. I was asked to add multiple calendar views and make the calendar embeddable on other websites.

I know what some of you are thinking. A calendar widget isn’t exactly a distributed systems problem, and one anecdote doesn’t prove much. Fair enough. But stick with me, because the point isn’t that this particular feature was hard — it’s that a chunk of real, working, deployed functionality that used to eat up an entire side-project weekend now takes less time than writing about it.

In the distant past — like four years ago — I would have spent time thinking through the architecture and design: deciding whether I needed to convert this static site into something with a back-end database, and researching JavaScript frameworks that provide this kind of calendar functionality. My guess is that this would have taken me somewhere between 16 and 40 hours. Since this is a side project, I’d only be working on it an hour or two at a time, so I’d probably tackle the month view first, get it working on desktop and mobile, then build the week view and the toggle between them. I’d also probably need to refactor some code so that the shareable calendar URL and the page hosting the calendar could use the same underlying code.

Now let me jump to how I completed this same request today.

I gave Claude Code the following instruction:

“I would like to allow the user to toggle between different calendar views similar to Google Calendar — weekly, monthly, and the current small monthly calendar with the list. Also, I would like other websites to be able to easily embed the calendar into their own pages.”

Claude responded by reading through the existing codebase and handing me a wonderfully detailed plan. It took about 3 minutes to put that plan together, running on Sonnet 5. I’m on a Claude Pro plan at $200/year, but between usage I typically spend another $30 to $40 a month on Claude on top of that. If you want to see exactly what that plan looked like rather than just take my word for it, I’ve posted the full plan and Claude’s complete output while it built the feature.

I told Claude to “go ahead” and execute its plan. That took about 10 minutes, during which I watched cat videos. Literally. Claude gave me updates along the way and a summary of the changes once it finished. Everything looked plausibly reasonable, so I told it to commit and push, which triggers an automatic deploy. I tested it on my live public website, and it worked and looked great. I tested it on my phone too, and it worked well there as well.

I should also mention: this project has no spec files and no markdown files for Claude to reference (though most of my other projects do). It took me far longer to write this blog post than it did to implement and ship all of these features to production. This didn’t just save me dozens of hours — it also saved me the frustration I would almost certainly have hit debugging code I wrote by hand that didn’t quite work and needed to be researched, fixed, and retested.

And honestly, the hours it saved weren’t even the hours I enjoyed. I didn’t miss out on some deeply satisfying architecture puzzle — I skipped the parts I never liked much anyway: digging through JavaScript calendar library docs, relearning CSS grid breakpoints for the tenth time, and combing Stack Overflow for why some cross-browser quirk was breaking my layout. AI didn’t replace the engineering. It replaced the tedious research I was doing around the engineering.

Being able to build and deploy a solid, robust solution this quickly feels wonderful. I know plenty of software features are more complex and demand more careful thought — security reviews, cost reviews, and real design work. For those, spec files (usually markdown), along with agents that perform additional review passes, are incredibly powerful tools.

If you’re still skeptical, I get it — I was too, not that long ago. You don’t have to hand over your core production system to find out what this is like. Try it on your next low-stakes side project, or on some feature that isn’t going through a production review this week. See what it’s actually like to work alongside it before you decide it’s not for you.

Posted in AI, Coding | Tagged: , , , | Leave a Comment »

Balancing Data Center Growth and Community Concerns

Posted by robkraft on May 14, 2026

Data center with glowing neon cables and racks labeled NET-PUNK OPS and NEON-CORE

1. Data centers have existed for decades. Without them, we wouldn’t have the internet, smartphones, websites, Facebook, Netflix, Amazon, X, or even email. Most people know this in the abstract, but given how much animosity has been directed at data centers over the past year or two, it’s worth saying plainly: tens of thousands of data centers around the world quietly power virtually everything we do “online.” A world without them isn’t a simpler or greener world — it’s a world without modern banking, hospitals, logistics, or communication. Whatever the right policy is, “no more data centers” isn’t it.

2. The concerns driving the backlash are not unreasonable, and pretending otherwise won’t win anyone over. Data centers use significant electricity and, depending on their cooling design, significant water. They can strain local grids, raise utility rates for nearby residents, generate low-frequency noise, and receive tax abatements that don’t always deliver the promised local jobs. Communities that feel they’re absorbing the costs while the benefits flow elsewhere have a legitimate grievance. The right response to those concerns is better siting, better cooling technology (air-cooled and closed-loop systems use far less water), honest negotiation over tax incentives, and grid investments paid for by the operators who drive the demand. The wrong response is to treat every proposed data center as inherently illegitimate.

3. We also need to keep building, because demand for computing is growing — and artificial intelligence has accelerated that demand sharply. AI may turn out to be the most significant technological shift since we harnessed electricity. That’s a prediction, not a fact, and reasonable people disagree about the magnitude. But even on conservative estimates, AI is already reshaping how knowledge work gets done, and the compute it requires has to physically exist somewhere.

4. Here’s the part that gets less attention in the debate: people with access to capable AI are going to be significantly more productive than people without it, especially for digital work. AI is expensive today because demand is strong, supply is limited, and energy costs are high. Wealthy people and large companies can comfortably afford current prices — and could still afford prices ten or a hundred times higher. If supply stays constrained, the productivity gains from AI will flow disproportionately to those who can already afford them, widening the gap between the wealthy and everyone else.

5. The only way to put AI within reach of ordinary people is to bring costs down, and the only way markets accomplish that is by expanding capacity. That means more data centers. The encouraging news is that the per-unit cost of AI has already been falling sharply as capacity has grown — evidence that the mechanism works when we let it.

Conclusion. None of this means every data center on a drawing board should be built. Given the pace of announcements, it’s entirely plausible that too many will be built too quickly, wasting capital, land, water, and power. Local communities deserve real input, real protections, and a real share of the benefits. But the opposite extreme — blocking new construction across the board — carries its own danger: that the productivity gains from AI flow only to those wealthy enough to afford scarce, expensive compute, while everyone else falls further behind. The honest debate isn’t “data centers, yes or no.” It’s where to build them, how to power them, who pays the costs, and who shares the gains. That’s the conversation worth having.


Posted in I.T. | Tagged: , , , , | Leave a Comment »

How to configure your MySQL database on AWS RDS to use IAM Authentication

Posted by robkraft on September 22, 2024

I was surprised by how difficult it was to set up a MySQL database on AWS RDS to use IAM Authentication.  IAM Authentication is superior to using connection strings because you don’t need a password to connect, therefore you don’t need to find a way to securely store and manage that password. 

Step 1: Configure RDS database for IAM Authentication

To set up your MySQL installation on AWS RDS you first need to configure your MySQL database in RDS to allow IAM Authentication.

At the time of this writing in September of 2024, you do that by navigating to your database in AWS RDS and selecting “Modify”.  In the Database authentication section, select “Password and IAM database authentication”

After doing so, you need to click the “Continue” button then “Modify DB Instance”.  The change does not happen immediately.  When I did this, I had to repeat the process several times because my change did not seem to take.  So you should go back into your database configuration to confirm the change took.  Also, I believe the database needs to be rebooted before it takes effect.  I recommend that you wait to move on to subsequent steps until you have confirmed, via the Modify UI for the database, that the Database authentication value is set correctly.

Step 2: Create a policy that allows connecting to the database

This step was the most problematic for me and took the longest to figure out.  I tried giving my Role all RDS policies I could find, but the only way I could solve the problem was to create a new policy.  This is extremely bizarre and I still feel I must have overlooked something. If you know what I missed, please let me know, but this step is the final action I took that allowed my application to work.

Create a new policy (because there is not one built into AWS) that allows for “connecting” to an RDS database.

In IAM, go to Policies, select “Create policy”, select a service which must be “RDS IAM Authentication”. Select the checkbox for “All RDS IAM Authentication actions” (there is only one).  Select “Any in this account” and “Specific” under Resources specify which AWS Resources can use this policy.  Give your policy a meaningful name such as “CustomPolicyForConnectingToRDS” and save it.

Step 3 Create a Role in AWS for the policy and services

The great thing, in my opinion, about building applications in AWS or Azure is the ability to security them easily with policies.  In this case, you need to now create a role that will have the new policy.  In AWS IAM, create a new role or alter an existing role to contain all the policies that your service calling RDS will need.  In my case, I will have several Lambdas that need to connect to my RDS MySQL database, so I created a role named LambdasToCallRDS and gave the role two permissions, the permission I created in Step 2 “CustomPolicyForConnectingToRDS” and also “AmazonS3ObjectLambdaExecutionPolicy”.

Step 4: Assign the role to your services

The last thing you need to do in AWS is to configure your service, in my case a Lambda, to have the role you created in Step 3.  So I went to the configuration of my Lambda and to the Permissions tab and assigned my role to the Lambda.

Step 5: Create user in MySQL

You need to create a user that logs in using AWS’s IAM.  I don’t think you can pick an existing user in MySQL that uses a login and password, but I am not sure about that.  I created a new user in RDS as shown here with the “IDENTIFIED WITH AWSAuthenticationPlugin” and limited the permissions of that user to the CRUD verbs.

CREATE USER ‘myrdsuser’@‘%’ IDENTIFIED WITH AWSAuthenticationPlugin as ‘RDS’;

GRANT SELECT, INSERT, UPDATE, DELETE ON reusefull.* TO ‘myrdsuser’@‘%’;

Step 6: Write some code to use IAM Authentication

The _dbUser is the name of the user you created in MySQL. In my example above, the name is myrdsuser.

Posted in Coding, Security | Tagged: , , , | 1 Comment »

How to turn off Microsoft SQL Server Extension Agent Setup

Posted by robkraft on August 15, 2024

I gave SQL Server’s “backup to Azure” feature a try. The feature rolled out in SQL Server 2022. It was fairly easy to set up, but it wasn’t for me, and it created a lot of traffic (SQL Queries) to my SQL Server. So I decided to turn it off. But that is incredibly hard. It is now a year later and I think I may have succeeded.

I started by trying to turn off services on my local PC related to it, but there was still SQL Traffic and errors in Event Viewer. Over the months, when I had time, I uninstalled SQL Server features using Add/Remove programs, but the problems kept coming. I restarted SQL Server after doing these things. I used resources at https://learn.microsoft.com/en-us/azure/backup/backup-sql-server-azure-troubleshoot to help identify things I might uninstall, delete, remove.

Finally today I found a task in Windows Task Scheduler, under Microsoft and under SqlServerExtension that I think is the culprit behind my woes. I deleted it, and am watching my logs, SQL Profiler traffic and hoping to see no more SQL Server Extension traffic or errors.

Posted in SQL Server | Leave a Comment »

How to UnZip, Unblock, and Enable a Microsoft Access Database Downloaded or Received via Email

Posted by robkraft on May 20, 2024

Microsoft Access databases downloaded from a website or received via an email can often be deployed in less than a minute.  But if you don’t do it often, you may be unaware of security features in Windows that may require a few extra steps. In short, the steps are 1) Unzip, 2) Unblock, 3) Confirm opening, and 4) Enable Content

  1. Unzip the file.

If the file is zipped, you must first unzip it.  Zipping files before sending them in email is common because it makes the files smaller, and sometimes helps keep the email servers from flagging the Access database attached to the email as possibly malicious.

  1. Unblock the file.

Windows automatically “blocks” some files when they are placed on your hard drive after being downloaded from the Internet or received via email.  The block is intended to prevent malware from automatically executing and damaging your computer.

  1. Confirm opening the file.

The first time you open a file that may trigger running code, Windows is likely to prompt you to confirm that you want to do so. Just click the Open button to do so.


  1. Open the database and Enable Content.

Like most products in the Microsoft Office Suite, Microsoft Access has a feature called Macros that exists to run code. Most Microsoft Access applications rely on Macros in order to work properly, though some do not. It is very likely that you need to click on “Enable Macros” to use your Access database.

That is likely all that you need to do for most Access Databases.

If you open the Access Database and see the “Security Risk Microsoft has blocked macros from running because the source of this file is untrusted.” error message shown below, go back to the properties of the file and make sure that it has been successfully “unblocked”.

Posted in Access, I.T. | Tagged: , , , | Leave a Comment »

Aim for product excellence, not project completion

Posted by robkraft on March 7, 2024

While it’s important to execute projects well, the true measure of success lies in the quality, impact, and longevity of the product, not the completion of the projects that built the product. Sacrifice a project for the good of the product.

If you have been working on a software development project that you expect to take three years to complete, and you learn something new along the way, or a new technology like chap-gpt comes along that gives you new ideas, don’t be afraid to cancel the project and call the project a failure. The product is more important than the projects used to build it. Always do what is best for the product, even if it means a project is delayed or cancelled.

Posted in Process, Project Management, Software Development | Leave a Comment »

I Don’t Always Have Time to Write Unit Tests…

Posted by robkraft on June 2, 2023

Posted in Coding, Humor | Leave a Comment »

Improving ASP.Net Framework Membership Performance

Posted by robkraft on April 9, 2023

I’ll admit this post would have been more valuable fifteen years ago, but those still using the ASP.Net Membership provider tables and stored procs that came with .Net Framework 2.0, 3.0, or 4.8 my like the performance boost you can get from these changes. The Membership tables were designed with two major flaws, in hindsight. One, the primary keys are guids, which provide terrible performance, and two, they assumed most installations would have multiple applications using the same membership table, which I think they do not. But Microsoft made the application ID, a guid, part of every key in every query. If your application only has a single application ID, you can change the stored procs to not consider the application ID as part of the query, and change the indexes to remove application ID as well. The change scripts are provided below:


alter PROCEDURE [dbo].[aspnet_Membership_GetUserByEmail]
    @ApplicationName  nvarchar(256),
    @Email            nvarchar(256)
AS
BEGIN
    IF( @Email IS NULL )
        SELECT  u.UserName
        FROM    dbo.aspnet_Users u, dbo.aspnet_Membership m
        WHERE  
               
                u.UserId = m.UserId AND
                m.LoweredEmail IS NULL
    ELSE
        SELECT  u.UserName
        FROM    dbo.aspnet_Users u, dbo.aspnet_Membership m
        WHERE  
                
                u.UserId = m.UserId AND
                @Email = m.LoweredEmail

    IF (@@rowcount = 0)
        RETURN(1)
    RETURN(0)
END
GO

alter PROCEDURE dbo.aspnet_Membership_FindUsersByEmail  
    @ApplicationName       nvarchar(256),  
    @EmailToMatch          nvarchar(256),  
    @PageIndex             int,  
    @PageSize              int  
AS  
BEGIN  
 
    -- Set the page bounds  
    DECLARE @PageLowerBound int  
    DECLARE @PageUpperBound int  
    DECLARE @TotalRecords   int  
    SET @PageLowerBound = @PageSize * @PageIndex  
    SET @PageUpperBound = @PageSize - 1 + @PageLowerBound  
  
    -- Create a temp table TO store the select results  
    CREATE TABLE #PageIndexForUsers  
    (  
        IndexId int IDENTITY (0, 1) NOT NULL,  
        UserId uniqueidentifier  
    )  
  
    -- Insert into our temp table  
    IF( @EmailToMatch IS NULL )  
        INSERT INTO #PageIndexForUsers (UserId)  
            SELECT u.UserId  
            FROM   dbo.aspnet_Users u, dbo.aspnet_Membership m  
            WHERE  m.UserId = u.UserId AND m.Email IS NULL  
            ORDER BY m.LoweredEmail  
    ELSE  
        INSERT INTO #PageIndexForUsers (UserId)  
            SELECT u.UserId  
            FROM   dbo.aspnet_Users u, dbo.aspnet_Membership m  
            WHERE  m.UserId = u.UserId AND m.LoweredEmail LIKE @EmailToMatch 
            ORDER BY m.LoweredEmail  
  
    SELECT  u.UserName, m.Email, m.PasswordQuestion, m.Comment, m.IsApproved,  
            m.CreateDate,  
            m.LastLoginDate,  
            u.LastActivityDate,  
            m.LastPasswordChangedDate,  
            u.UserId, m.IsLockedOut,  
            m.LastLockoutDate  
    FROM   dbo.aspnet_Membership m, dbo.aspnet_Users u, #PageIndexForUsers p  
    WHERE  u.UserId = p.UserId AND u.UserId = m.UserId AND  
           p.IndexId >= @PageLowerBound AND p.IndexId <= @PageUpperBound  
    ORDER BY m.LoweredEmail  
  
    SELECT  @TotalRecords = COUNT(*)  
    FROM    #PageIndexForUsers  
    RETURN @TotalRecords  
END  
go

ALTER PROCEDURE [dbo].[aspnet_Membership_FindUsersByName]
    @ApplicationName       nvarchar(256),
    @UserNameToMatch       nvarchar(256),
    @PageIndex             int,
    @PageSize              int
AS
BEGIN

    -- Set the page bounds
    DECLARE @PageLowerBound int
    DECLARE @PageUpperBound int
    DECLARE @TotalRecords   int
    SET @PageLowerBound = @PageSize * @PageIndex
    SET @PageUpperBound = @PageSize - 1 + @PageLowerBound

    -- Create a temp table TO store the select results
    CREATE TABLE #PageIndexForUsers
    (
        IndexId int IDENTITY (0, 1) NOT NULL,
        UserId uniqueidentifier
    )

    -- Insert into our temp table
    INSERT INTO #PageIndexForUsers (UserId)
        SELECT u.UserId
        FROM   dbo.aspnet_Users u, dbo.aspnet_Membership m
        WHERE m.UserId = u.UserId AND u.LoweredUserName LIKE @UserNameToMatch
        ORDER BY u.UserName


    SELECT  u.UserName, m.Email, m.PasswordQuestion, m.Comment, m.IsApproved,
            m.CreateDate,
            m.LastLoginDate,
            u.LastActivityDate,
            m.LastPasswordChangedDate,
            u.UserId, m.IsLockedOut,
            m.LastLockoutDate
    FROM   dbo.aspnet_Membership m, dbo.aspnet_Users u, #PageIndexForUsers p
    WHERE  u.UserId = p.UserId AND u.UserId = m.UserId AND
           p.IndexId >= @PageLowerBound AND p.IndexId <= @PageUpperBound
    ORDER BY u.UserName

    SELECT  @TotalRecords = COUNT(*)
    FROM    #PageIndexForUsers
    RETURN @TotalRecords
END
GO




ALTER PROCEDURE [dbo].[aspnet_Membership_GetUserByName]
    @ApplicationName      nvarchar(256),
    @UserName             nvarchar(256),
    @CurrentTimeUtc       datetime,
    @UpdateLastActivity   bit = 0
AS
BEGIN
    DECLARE @UserId uniqueidentifier

    IF (@UpdateLastActivity = 1)
    BEGIN
        -- select user ID from aspnet_users table
        SELECT TOP 1 @UserId = u.UserId
        FROM     dbo.aspnet_Users u, dbo.aspnet_Membership m
        WHERE   
                @UserName = u.LoweredUserName AND u.UserId = m.UserId

        IF (@@ROWCOUNT = 0) -- Username not found
            RETURN -1

        UPDATE   dbo.aspnet_Users
        SET      LastActivityDate = @CurrentTimeUtc
        WHERE    @UserId = UserId

        SELECT m.Email, m.PasswordQuestion, m.Comment, m.IsApproved,
                m.CreateDate, m.LastLoginDate, u.LastActivityDate, m.LastPasswordChangedDate,
                u.UserId, m.IsLockedOut, m.LastLockoutDate
        FROM    dbo.aspnet_Users u, dbo.aspnet_Membership m
        WHERE  @UserId = u.UserId AND u.UserId = m.UserId 
    END
    ELSE
    BEGIN
        SELECT TOP 1 m.Email, m.PasswordQuestion, m.Comment, m.IsApproved,
                m.CreateDate, m.LastLoginDate, u.LastActivityDate, m.LastPasswordChangedDate,
                u.UserId, m.IsLockedOut,m.LastLockoutDate
        FROM     dbo.aspnet_Users u, dbo.aspnet_Membership m
        WHERE   
                @UserName = u.LoweredUserName AND u.UserId = m.UserId

        IF (@@ROWCOUNT = 0) -- Username not found
            RETURN -1
    END

    RETURN 0
END
GO


alter PROCEDURE [dbo].[aspnet_Roles_RoleExists]
    @ApplicationName  nvarchar(256),
    @RoleName         nvarchar(256)
AS
BEGIN
    IF (EXISTS (SELECT RoleName FROM dbo.aspnet_Roles WHERE @RoleName = LoweredRoleName  ))
        RETURN(1)
    ELSE
        RETURN(0)
END
GO



drop index aspnet_Membership_index on [dbo].[aspnet_Membership]
go
CREATE CLUSTERED INDEX [aspnet_Membership_index] ON [dbo].[aspnet_Membership]
(
	[LoweredEmail] ASC
)WITH (FillFactor=95) ON [PRIMARY]

GO

CREATE NONCLUSTERED INDEX [IX_ApplicationId] ON [dbo].[aspnet_Membership]
(
	[ApplicationId] ASC
)WITH (FillFactor=95)

GO

drop index aspnet_Users_index on [dbo].[aspnet_Users]
go
CREATE UNIQUE CLUSTERED INDEX [aspnet_Users_Index] ON [dbo].[aspnet_Users]
(
	[LoweredUserName] ASC
)WITH (FillFactor=95)

GO
drop index aspnet_Users_index2 on [dbo].[aspnet_Users]
go
CREATE NONCLUSTERED INDEX [aspnet_Users_Index2] ON [dbo].[aspnet_Users]
(
	[LastActivityDate] ASC
)WITH (FillFactor=95)

CREATE NONCLUSTERED INDEX [IX_AspNet_Users_ApplicationID] ON [dbo].[aspnet_Users]
(
	[ApplicationId] ASC
)WITH (FillFactor=95)

GO

Posted in Coding, SQL Server | Tagged: | Leave a Comment »

How to enable Rich Text for Text Boxes in Access Linked to SQL Server

Posted by robkraft on February 12, 2022

Text Boxes on Forms and Reports in Microsoft Access allow you to configure the field to display “Rich Text” instead of “Plain Text” by setting the “Text Format” property of the control. This allows you to use HTML formatting in the field and display portions of the field using bold, italics, underline, and other enhancements.

However, when your data is coming from a SQL Server (or other DBMS), through linked tables, you can only choose to configure the control to support “Rich Text” when the field has a size of 256 bytes or greater.

Therefore, you need to go back to your source database (SQL Server), increase the size of the field so that it is 256 or larger, then go back to Access and Relink the table. After that, you should be able to choose Rich Text for the field on the form.

When the linked field length is too short, you will get this message from Microsoft Access “The setting you entered isn’t valid for this property.” when you try to change the control.

Posted in Access, SQL Server | Leave a Comment »

Resolving SQL Server DatabaseMail Error:  Cannot send mails to mail server. (Failure sending mail.)

Posted by robkraft on February 7, 2022

I learned some information I thought could help others more quickly resolve this error on SQL Server. As security demands increase, some features of older versions of products get more difficult to maintain. In this case, I am supporting a SQL Server running on SQL Server 2014. It has the latest Cumulative Update applied (CU) yet the emails sent via DatabaseMail are still failing. They were failing occasionally but began to fail consistently after applying one of those recent updates. In conjunction with the updates we applied registry entry changes in an attempt to get SQL Server to support TLS 1.2+ with Microsoft Office Email on Smtp.office365.com port 587.

The error we received from the failed emails was very vague:

The mail could not be sent to the recipients because of the mail server failure. (Sending Mail using Account 3 (2022-01-31T06:18:01). Exception Message: Cannot send mails to mail server. (Failure sending mail.).

I used this simple script to test sending emails:

declare @rc int
  exec @rc = msdb.dbo.sp_send_dbmail
      @profile_name = N'SQLSend',
      @recipients         =  'myemail@mail.com',
      @subject    = N'*** Test by me ***',
      @body     = 'email body'

I wrote a C# .Net program to see if I could get more error detail. I didn’t know what version of .Net Framework was on the server so I compiled against .Net 2.0 and ran my program and got the same error. Then I compiled my program on .Net 4.8 and got a different error. What I learned was that the newer version of the .Net framework (anything 4.0 or higher) provides a more detailed error message.

So my next task was to figure out if SQL Server is using the .Net Framework to send the Email (which I think it is) and how to tell SQL Server to use a newer version of the .Net Framework.

SQL Server uses an executable named DatabaseMail.exe to send the email. That program will exist in a folder named something like: D:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\Binn

There program is a .Net program and therefore includes a .Config file. Or, I should say, it “can” include a config file. I think what happened is that .config file for our DatabaseMail.exe got deleted when an SQL Server update was installed (as mentioned in this article: https://support.microsoft.com/en-us/topic/kb3186435-fix-sql-server-2016-database-mail-does-not-work-on-a-computer-that-does-not-have-the-net-framework-3-5-installed-or-stops-working-after-applying-sql-server-update-3480beb6-1329-74d6-0f3a-e8e3d893326c

So I used notepad to create a new file named databasemail.exe.config in the same folder, and I put this content into the file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="DatabaseServerName" value="." />
    <add key="DatabaseName" value="msdb" />
  </appSettings>
  <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

By telling DatabaseMail.exe to use .Net Framework 4.5, we were able to get a more detailed error message from the failed emails. In our case, the error we got by using .Net Framework 4.5 is shown below. Our belief is that one of the SQL Server updates somehow altered the existing configured password of the hashing or encryption algorithm used for it. We simply re-entered our password in DatabaseMail configuration and our email started working again.

The mail could not be sent to the recipients because of the mail server failure. (Sending Mail using Account 3 (2022-02-01T12:47:47). Exception Message: Cannot send mails to mail server. (The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.7.57 Client not authenticated to send mail. Error: 535 5.7.139 Authentication unsuccessful, the user credentials were incorrect.

Posted in I.T., SQL Server | 5 Comments »

 
Design a site like this with WordPress.com
Get started