<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Georg Piwonka</title>
    <description>The latest articles on DEV Community by Georg Piwonka (@georgpiwonka).</description>
    <link>https://dev.to/georgpiwonka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3192727%2Ffc76ef38-eaba-4d26-91b0-872ccf24e709.jpg</url>
      <title>DEV Community: Georg Piwonka</title>
      <link>https://dev.to/georgpiwonka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/georgpiwonka"/>
    <language>en</language>
    <item>
      <title>Enterprise SQL Server Index Maintenance: Multi-Database Automation with Email Reporting</title>
      <dc:creator>Georg Piwonka</dc:creator>
      <pubDate>Sun, 22 Jun 2025 20:14:51 +0000</pubDate>
      <link>https://dev.to/georgpiwonka/enterprise-sql-server-index-maintenance-multi-database-automation-with-email-reporting-39b</link>
      <guid>https://dev.to/georgpiwonka/enterprise-sql-server-index-maintenance-multi-database-automation-with-email-reporting-39b</guid>
      <description>&lt;p&gt;Modern database environments require sophisticated maintenance strategies that can handle multiple databases efficiently while providing comprehensive reporting. This article presents an enterprise-grade T-SQL script that automates index maintenance across multiple databases, supports both tables and indexed views, and includes intelligent email reporting for proactive database administration.&lt;br&gt;
The Evolution: From Single Database to Enterprise Scale&lt;br&gt;
While basic index maintenance scripts focus on individual databases, enterprise environments demand solutions that can:&lt;/p&gt;

&lt;p&gt;Process multiple databases with configurable scope&lt;br&gt;
Provide comprehensive reporting via automated email notifications&lt;br&gt;
Handle different object types (tables and indexed views) intelligently&lt;br&gt;
Integrate seamlessly with SQL Server Agent for automation&lt;br&gt;
Scale efficiently across large database environments&lt;/p&gt;

&lt;p&gt;Enterprise-Grade Features&lt;br&gt;
Multi-Database Processing Options&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Database Scope Configuration&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;DatabaseScope&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ALL_USER'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;-- Options: 'CURRENT', 'ALL_USER', 'SPECIFIC'&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;SpecificDatabase&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MyDatabase'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- Used with 'SPECIFIC' scope&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three flexible modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CURRENT: Process only the current database (perfect for database-specific jobs)
2
ALL_USER: Process all user databases automatically (enterprise-wide maintenance)
SPECIFIC: Target a single named database (selective maintenance)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Intelligent Email Reporting&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Email Configuration&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;SendEmail&lt;/span&gt; &lt;span class="nb"&gt;BIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                             &lt;span class="c1"&gt;-- Enable email reports&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;EmailProfile&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Default'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;-- Database Mail profile&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;EmailRecipients&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dba@company.com;admin@company.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;EmailSubjectPrefix&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'[SQL Server]'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smart email features:&lt;/p&gt;

&lt;p&gt;Contextual subject lines: "ERRORS ENCOUNTERED", "MAINTENANCE COMPLETED", or "NO ACTION REQUIRED"&lt;br&gt;
Detailed database-by-database breakdown&lt;br&gt;
Comprehensive execution summary with success/failure tracking&lt;br&gt;
Error alerting with specific failure details&lt;/p&gt;

&lt;p&gt;Advanced Architecture&lt;br&gt;
Cross-Database Statistics Collection&lt;br&gt;
The script employs dynamic SQL to collect fragmentation statistics across multiple databases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Dynamic cross-database analysis&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;SQL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'
USE ['&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;CurrentDatabase&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'];
INSERT INTO #IndexStats (...)
SELECT ... 
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, &lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;DETAILED&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;) ips
-- ... comprehensive analysis logic
'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sp_executesql&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures:&lt;/p&gt;

&lt;p&gt;Consistent analysis across all target databases&lt;br&gt;
Centralized result aggregation for comprehensive reporting&lt;br&gt;
Database-specific context preservation&lt;br&gt;
Efficient resource utilization&lt;/p&gt;

&lt;p&gt;Execution Status Tracking&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```sql-- Execution tracking with detailed status&lt;br&gt;
ALTER TABLE #IndexStats ADD ExecutionStatus NVARCHAR(50) DEFAULT 'PENDING';&lt;/p&gt;

&lt;p&gt;-- Status updates during execution&lt;br&gt;
UPDATE #IndexStats &lt;br&gt;
SET ExecutionStatus = CASE &lt;br&gt;
    WHEN &lt;a class="mentioned-user" href="https://dev.to/success"&gt;@success&lt;/a&gt; = 1 THEN 'SUCCESS'&lt;br&gt;
    ELSE 'ERROR: ' + ERROR_MESSAGE()&lt;br&gt;
END;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Email Report Structure
The automated email reports provide comprehensive insights:
Executive Summary

Total databases processed
Aggregate index statistics
Overall success/failure metrics
Error count and types

Database-Specific Details

Per-database index counts
Fragmentation statistics
Maintenance actions performed
Execution results

Detailed Action Log

Individual index maintenance commands
Success/failure status for each operation
Fragmentation percentages before maintenance
Error details for failed operations

Production Deployment Strategies
SQL Server Agent Integration
Job 1: Weekly Analysis


```sql-- Configuration for analysis-only run
DECLARE @ExecuteCommands BIT = 0;  -- Analysis mode
DECLARE @DatabaseScope NVARCHAR(20) = 'ALL_USER';
DECLARE @SendEmail BIT = 1;        -- Send analysis report
Job 2: Weekend Maintenance
sql-- Configuration for full maintenance
DECLARE @ExecuteCommands BIT = 1;  -- Execute maintenance
DECLARE @DatabaseScope NVARCHAR(20) = 'ALL_USER';
DECLARE @SendEmail BIT = 1;        -- Send completion report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Environment-Specific Configurations&lt;br&gt;
Development Environment:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```sql-- Aggressive maintenance for development&lt;br&gt;
DECLARE @FragmentationThresholdReorganize FLOAT = 5.0;&lt;br&gt;
DECLARE @FragmentationThresholdRebuild FLOAT = 20.0;&lt;br&gt;
DECLARE @DatabaseScope NVARCHAR(20) = 'ALL_USER';&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Production Environment:


```sql
-- Conservative settings for production
DECLARE @FragmentationThresholdReorganize FLOAT = 15.0;
DECLARE @FragmentationThresholdRebuild FLOAT = 40.0;
DECLARE @DatabaseScope NVARCHAR(20) = 'SPECIFIC';  -- Controlled targeting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Advanced Email Configuration&lt;br&gt;
Database Mail Setup&lt;br&gt;
Before using the email features, ensure Database Mail is configured:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```sql-- Enable Database Mail&lt;br&gt;
sp_configure 'show advanced options', 1;&lt;br&gt;
RECONFIGURE;&lt;br&gt;
sp_configure 'Database Mail XPs', 1;&lt;br&gt;
RECONFIGURE;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

-- Create mail profile (example)


```sql
EXEC msdb.dbo.sysmail_add_profile_sp
    @profile_name = 'DBA Notifications',
    @description = 'Database maintenance notifications';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Recipient Management&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```sql-- Multiple recipients with semicolon separation&lt;br&gt;
DECLARE @EmailRecipients NVARCHAR(MAX) = '&lt;a href="mailto:dba-team@company.com"&gt;dba-team@company.com&lt;/a&gt;;&lt;a href="mailto:infrastructure@company.com"&gt;infrastructure@company.com&lt;/a&gt;;&lt;a href="mailto:manager@company.com"&gt;manager@company.com&lt;/a&gt;';&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Monitoring and Alerting Integration
SCOM/Nagios Integration
The email reports can be parsed by monitoring systems for automated alerting:


```sql-- Error-specific subject line for monitoring systems
SET @EmailSubject = @EmailSubjectPrefix + ' Index Maintenance Report - ' + 
                   CASE 
                       WHEN @TotalErrors &amp;gt; 0 THEN 'ERRORS ENCOUNTERED'
                       -- ... other conditions
                   END;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;PowerBI Dashboard Integration&lt;br&gt;
The script's output can feed into PowerBI dashboards for trend analysis:&lt;/p&gt;

&lt;p&gt;Historical fragmentation trends&lt;br&gt;
Maintenance success rates&lt;br&gt;
Database growth patterns&lt;br&gt;
Performance impact analysis&lt;/p&gt;

&lt;p&gt;Performance Considerations&lt;br&gt;
Resource Management&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Staggered execution to manage resource usage&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;maintenance_cursor&lt;/span&gt; &lt;span class="k"&gt;CURSOR&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;DatabaseName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaintenanceCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RecommendedAction&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;IndexStats&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;MaintenanceCommand&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DatabaseName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                    &lt;span class="c1"&gt;-- Database grouping&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;ObjectType&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'TABLE'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- Tables first&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;RecommendedAction&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'REBUILD'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- Rebuilds first&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Memory Optimization&lt;/p&gt;

&lt;p&gt;Cursor-based processing to handle large result sets efficiently&lt;br&gt;
Dynamic SQL execution to minimize memory footprint per database&lt;br&gt;
Temporary table cleanup to prevent memory leaks&lt;/p&gt;

&lt;p&gt;Security and Compliance&lt;br&gt;
Permission Requirements&lt;br&gt;
The script requires specific permissions:&lt;/p&gt;

&lt;p&gt;db_owner on target databases for index maintenance&lt;br&gt;
DatabaseMailUserRole in msdb for email functionality&lt;br&gt;
View any database for cross-database statistics collection&lt;/p&gt;

&lt;p&gt;Audit Trail&lt;br&gt;
All activities are logged and reported:&lt;/p&gt;

&lt;p&gt;Command execution details in email reports&lt;br&gt;
Error logging with full context&lt;br&gt;
Success metrics for compliance reporting&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/gpiwonka/SQLScripts/blob/master/IndexMaintenanceScript.sql" rel="noopener noreferrer"&gt;https://github.com/gpiwonka/SQLScripts/blob/master/IndexMaintenanceScript.sql&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;more ... &lt;a href="https://piwonka.cc" rel="noopener noreferrer"&gt;https://piwonka.cc&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sqlserver</category>
      <category>sql</category>
      <category>database</category>
    </item>
    <item>
      <title>From WordPress to ASP.NET Core: A Development Adventure during Le Mans</title>
      <dc:creator>Georg Piwonka</dc:creator>
      <pubDate>Sun, 15 Jun 2025 18:33:09 +0000</pubDate>
      <link>https://dev.to/georgpiwonka/from-wordpress-to-aspnet-core-a-development-adventure-during-le-mans-44p6</link>
      <guid>https://dev.to/georgpiwonka/from-wordpress-to-aspnet-core-a-development-adventure-during-le-mans-44p6</guid>
      <description>&lt;p&gt;It's time for the 24 Hours of Le Mans and the Formula 1 Canadian Grand Prix are here. For a motorsport heart, there's hardly anything better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea: Breaking Free from WordPress
&lt;/h2&gt;

&lt;p&gt;I've been carrying the idea for a while to liberate my website from WordPress and instead create a simple, self-developed solution. This would save me all the overhead. As a C# developer, PHP and Python are rather off-putting to me – admittedly a very personal opinion. Anyone who has programmed in these languages for years probably finds C# just as terrible.&lt;/p&gt;

&lt;p&gt;To turn the more or less sensible "driving around in circles stupidly" (as Niki Lauda once put it) into something constructive, I thought: Why not build something in ASP.NET Core and C# during Le Mans? I had started some time ago but didn't get further due to time constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Requirements
&lt;/h2&gt;

&lt;p&gt;My ideas were deliberately kept simple:&lt;/p&gt;

&lt;p&gt;A simple blog with categories, but without comment functionality&lt;br&gt;
A few static pages – basically a small CMS&lt;br&gt;
An admin area for content entry&lt;br&gt;
As a challenge: multilingual support&lt;br&gt;
Everything as simple as possible&lt;br&gt;
Claude as Development Partner&lt;br&gt;
While experimenting, I noticed that Claude can generate pretty good code and often helps more effectively with problems than a Google or Bing search. So why not get Claude to help?&lt;/p&gt;

&lt;p&gt;When Le Mans started, I began with a naive prompt: "I want to build my website with .NET Core Razor. I want a very simple design and a small backend for my posts. For the backend, I want MSSQL with Entity Framework. Can you help me with this?"&lt;/p&gt;

&lt;p&gt;And off we went. Claude produced code and I could watch Le Mans in parallel. 😊&lt;/p&gt;

&lt;h2&gt;
  
  
  First Successes
&lt;/h2&gt;

&lt;p&gt;The generated code was comprehensive and really well-suited for a start. I created a Visual Studio project, copied the code in – and everything worked. It doesn't get faster than this: I had a site with blog functionality and a corresponding admin area.&lt;/p&gt;

&lt;p&gt;Since I wasn't precise enough, I still had to adjust the namespaces. This is essential, by the way: The more specific requirements, the better the code becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interesting Observations
&lt;/h2&gt;

&lt;p&gt;From here it got really interesting. The longer the chat lasted, the more inconsistent the code became and the more variable the patterns that Claude applied. Sometimes with ViewModels, sometimes without, sometimes services, sometimes static classes. I found this fascinating since I had actually expected consistent code.&lt;/p&gt;

&lt;p&gt;But it wasn't a big problem – until after a few hours the maximum chat length was reached.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge: Chat Limitations
&lt;/h2&gt;

&lt;p&gt;This is where it got complicated. Unfortunately, Claude can't build on previous chats, but knowledge of already created code would have been advantageous.&lt;/p&gt;

&lt;p&gt;Claude Projects sounded like a solution, but neither chat publishing nor the GitHub reference worked for Claude. So I added the entire source code file by file to the project knowledge. In retrospect, it would have been better to start with a project right away – but this way I could watch more Le Mans...&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Loops and Misunderstandings
&lt;/h2&gt;

&lt;p&gt;Further development went quite smoothly, except for some loops where Claude and I didn't understand each other. There were quite amusing misunderstandings: When I wanted to simplify the language model and handling, Claude made an entire translation system out of it.&lt;/p&gt;

&lt;p&gt;Sometimes he was also stubborn and absolutely refused to rebuild certain code parts.&lt;/p&gt;

&lt;p&gt;Recurring Errors&lt;br&gt;
It's interesting that certain errors persist stubbornly. Claude likes to use inline styles, which is basically okay. However, with &lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; and @keyframes, he occasionally forgets the first or second @ because these need to be escaped in Razor pages.&lt;/p&gt;

&lt;p&gt;It proved particularly difficult to undo certain design decisions by Claude. But in the end, everything worked out well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Review with an AI Partner
&lt;/h2&gt;

&lt;p&gt;At times it felt like a long code review session. I left most of the code as Claude had produced it. However, in hindsight, the code still needs to be reworked with regard to performance and standardization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It was an entertaining experiment – especially when Claude gets fixated on topics and absolutely refuses to listen to his "master." This reminds me of some of my juniors.&lt;/p&gt;

&lt;p&gt;And Le Mans? That was also very exciting: no long safety car phases and everyone always pushing. Robert Kubica, Yifei Ye, and Phil Hanson won – very impressively. The Austrian Richard Lietz triumphed together with Ryan Hardwick (USA) and Riccardo Pera (ITA) in a Manthey Porsche in the LMGT3 category.&lt;/p&gt;

&lt;p&gt;A perfect weekend for motorsport and code!&lt;/p&gt;

&lt;p&gt;Site: &lt;a href="https://piwonka.cc" rel="noopener noreferrer"&gt;piwonka.cc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Site: &lt;a href="https://github.com/gpiwonka/piwonka.cc" rel="noopener noreferrer"&gt;repository&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>aspdotnet</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building SmartReceiptOrganizer: An AI-Powered Email Receipt Processing System</title>
      <dc:creator>Georg Piwonka</dc:creator>
      <pubDate>Wed, 04 Jun 2025 21:18:19 +0000</pubDate>
      <link>https://dev.to/georgpiwonka/building-smartreceiptorganizer-an-ai-powered-email-receipt-processing-system-4lak</link>
      <guid>https://dev.to/georgpiwonka/building-smartreceiptorganizer-an-ai-powered-email-receipt-processing-system-4lak</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I created &lt;strong&gt;SmartReceiptOrganizer&lt;/strong&gt; - an automated receipt processing system that turns your email inbox into a smart financial database. The system receives receipt emails via Postmark's inbound webhooks, automatically detects PDF attachments, and uses AI to extract key financial data like amounts, currencies, dates, and merchant names.&lt;/p&gt;

&lt;p&gt;Think of it as your personal AI accountant that never sleeps! 🤖💰&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📧 &lt;strong&gt;Automatic Email Processing&lt;/strong&gt;: Receives receipts via Postmark webhooks&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Smart PDF Detection&lt;/strong&gt;: Handles various content types (even when PDFs are sent as &lt;code&gt;application/octet-stream&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;AI Data Extraction&lt;/strong&gt;: Uses Mindee AI to extract amounts, currencies, dates, and merchants&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;Intelligent Storage&lt;/strong&gt;: Saves everything in a searchable database&lt;/li&gt;
&lt;li&gt;🛡️ &lt;strong&gt;Robust Error Handling&lt;/strong&gt;: Bulletproof DateTime parsing and duplicate detection&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;REST API&lt;/strong&gt;: Full CRUD operations and statistics endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live API Endpoints:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET http://postmandevto.runasp.net/api/receipts&lt;/code&gt; - View all processed receipts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET http://postmandevto.runasp.net/api/receipts/stats&lt;/code&gt; - Financial statistics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET http://postmandevto.runasp.net/api/mindee-debug/config&lt;/code&gt; - System health check&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Test:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send an email with a PDF receipt to &lt;strong&gt;any&lt;/strong&gt; of these addresses:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;devto_postmark_invoice@piwonka.cc&lt;/code&gt; &lt;em&gt;(special address for dev.to community testing!)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Check the processed data via the API endpoints above&lt;/li&gt;
&lt;li&gt;The system automatically extracts financial data from the PDF and responds within seconds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Sample API Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"receiptsCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"receipts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"merchant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MTEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;29.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"transactionDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-06-01T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"originalEmailSubject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WG: MTEL Monatsrechnung"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attachmentCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hasMindeeData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;

&lt;p&gt;The complete source code is available on GitHub: &lt;a href="https://github.com/gpiwonka/SmartReceiptOrganizer" rel="noopener noreferrer"&gt;SmartReceiptOrganizer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key files to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/your-username/SmartReceiptOrganizer/blob/main/Controllers/PostmarkReceiptController.cs" rel="noopener noreferrer"&gt;&lt;code&gt;Controllers/PostmarkReceiptController.cs&lt;/code&gt;&lt;/a&gt; - Main webhook handler&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/your-username/SmartReceiptOrganizer/blob/main/Models/Postmark/PostmarkInboundMessage.cs" rel="noopener noreferrer"&gt;&lt;code&gt;Models/Postmark/PostmarkInboundMessage.cs&lt;/code&gt;&lt;/a&gt; - Postmark integration models&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/your-username/SmartReceiptOrganizer/blob/main/Controllers/MindeeDebugController.cs" rel="noopener noreferrer"&gt;&lt;code&gt;Controllers/MindeeDebugController.cs&lt;/code&gt;&lt;/a&gt; - AI debugging tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: ASP.NET Core 8 (C#)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: SQL Server with Entity Framework Core&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email Processing&lt;/strong&gt;: Postmark Inbound Webhooks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: Mindee Receipt API for data extraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting&lt;/strong&gt;: monsterasp.net (Free Tier)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Journey
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Challenge #1: Postmark DateTime Formats&lt;/strong&gt; 🕒&lt;br&gt;
Postmark sends dates in RFC 2822 format (&lt;code&gt;"Tue, 3 Jun 2025 21:46:05 +0000"&lt;/code&gt;), but .NET's JsonSerializer expects ISO 8601. My solution was elegantly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonPropertyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;DateString&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Parse as string first!&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ParseDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Multiple format parsing with fallbacks&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;formats&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"ddd, d MMM yyyy HH:mm:ss zzz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"ddd, dd MMM yyyy HH:mm:ss zzz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// ... more formats&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;// Robust parsing logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Challenge #2: PDF Content Type Detection&lt;/strong&gt; 📄&lt;br&gt;
Discovered that Postmark sometimes sends PDFs as &lt;code&gt;application/octet-stream&lt;/code&gt; instead of &lt;code&gt;application/pdf&lt;/code&gt;. Built smart detection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsPdfAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PostmarkAttachment&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Check ContentType&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Fallback: check filename extension&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
        &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;EndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".pdf"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Challenge #3: AI Integration&lt;/strong&gt; 🤖&lt;br&gt;
Integrating Mindee's Receipt API was surprisingly smooth. The trickiest part was handling the multipart form data properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;formContent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MultipartFormDataContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileContent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ByteArrayContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MediaTypeHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;formContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"receipt.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;_httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;$"Token &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mindeeApiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Challenge #4: Free Hosting Constraints&lt;/strong&gt; 💰&lt;br&gt;
Running on free tier means no HTTPS, but Postmark's flexible webhook configuration made this a non-issue. The HTTP endpoint works perfectly for webhook delivery!&lt;/p&gt;

&lt;h3&gt;
  
  
  Postmark Experience
&lt;/h3&gt;

&lt;p&gt;Working with Postmark was a &lt;strong&gt;fantastic&lt;/strong&gt; experience! The inbound webhook system is incredibly reliable and well-documented. What impressed me most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Flexible Webhook Configuration&lt;/strong&gt;: Works seamlessly with HTTP endpoints (perfect for free hosting!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent JSON Structure&lt;/strong&gt;: The webhook payload is always predictable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich Attachment Support&lt;/strong&gt;: Base64 encoding makes file handling straightforward
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excellent Documentation&lt;/strong&gt;: Clear examples and troubleshooting guides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable Delivery&lt;/strong&gt;: Never missed a webhook during development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Email Address Support&lt;/strong&gt;: Easy to set up multiple inbound addresses for different use cases&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The only "gotcha" was the DateTime format difference, but that's more about different standards than a Postmark issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture Highlights
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent Processing&lt;/strong&gt;: Duplicate detection prevents reprocessing the same email&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful Degradation&lt;/strong&gt;: System works even if AI extraction fails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive Logging&lt;/strong&gt;: Detailed request tracking for debugging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RESTful API&lt;/strong&gt;: Clean endpoints for integration with other systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Address Support&lt;/strong&gt;: &lt;code&gt;devto_postmark_invoice@piwonka.cc&lt;/code&gt; are processed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real-World Testing
&lt;/h3&gt;

&lt;p&gt;The system is actively processing receipts! Here's what happens when you send a PDF receipt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Email arrives&lt;/strong&gt; at either address&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postmark webhook fires&lt;/strong&gt; within seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF is extracted&lt;/strong&gt; and saved to database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mindee AI analyzes&lt;/strong&gt; the receipt content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured data&lt;/strong&gt; (amount, merchant, date) is extracted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything is stored&lt;/strong&gt; and immediately queryable via API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This project showcases how &lt;strong&gt;Postmark's reliable email infrastructure&lt;/strong&gt; combined with &lt;strong&gt;modern AI services&lt;/strong&gt; can create powerful automation solutions. It's been an absolute joy to build!&lt;/p&gt;

&lt;p&gt;The system is now processing real receipts and saving me hours of manual data entry. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it yourself by sending a PDF receipt to &lt;code&gt;devto_postmark_invoice@piwonka.cc&lt;/code&gt; and watch the magic happen!&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with ❤️ using Postmark, ASP.NET Core, and a healthy dose of debugging coffee ☕&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
  </channel>
</rss>
