prod@blog:~$

Building an ASP.NET End of Day Incident Report System

Manual processes are the enemy of consistency, especially when it comes to critical communications like incident reporting. I recently built an ASP.NET Web Forms application to replace a manual email process that was prone to errors and inconsistencies. Here's how I created a two-tier incident reporting system that ensures reliable, formatted reports go to the right people every time.

The Problem

The Service Desk team was manually composing end-of-day incident summary emails in Outlook. This led to several issues but the main issue was sometimes that report would not come from the shared mailbox but the individuals account.

I needed a solution that would enforce consistency while remaining simple enough for any team member to use.

Visual Images

This is the main "production" version that will be sent to all concerned parties first we have the outstanding incidents that are only including P1/P2 incidents:

Then we have underneath that the resolved issues for the day which covers P1/P2 incidents, here you can also see the send button:

If we then choose to use the simple version you can simply click on the hyperlink as below:


If you use decide to use the manual version, you have to enter the recipient emails addresses with comma separated values before you can send the report, this is more there for testing to system:


The Solution: A Two-Tier Reporting System

I built a web-based reporting system with two versions:

  1. Production Version: Fixed recipients, foolproof operation
  2. Test/Non-Live Version: Flexible recipient selection for testing or individual notifications

Complete Code

Here's the complete ASPX page for the production version:

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System.Net.Mail" %>

<!DOCTYPE html>
<script runat="server">
    // Configuration
    private const string SMTP_SERVER = "smtprelay.stwater.intra";
    private const string FROM_EMAIL = "is.servicedesk@severntrent.co.uk";
    private const string FIXED_RECIPIENTS = "Servicedesk24@severntrent.co.uk,opsbridge@severntrent.co.uk,ISSCDL@severntrent.co.uk";
    
    // Authorized users - add Windows usernames here (without domain)
    private readonly string[] AUTHORIZED_USERS = new string[] {
        "lee.croucher",
        "service.desk",
    };

    protected void Page_Load(object sender, EventArgs e)
    {
        // Check if user is authenticated
        if (!IsUserAuthorized())
        {
            ShowAccessDeniedPanel();
            return;
        }

        ShowReportPanel();
        
        if (!IsPostBack)
        {
            // Set today's date
            lblReportDate.Text = DateTime.Now.ToString("dd/MM/yy");
        }
    }

    private bool IsUserAuthorized()
    {
        if (!User.Identity.IsAuthenticated)
            return false;

        // Get username without domain
        string username = User.Identity.Name;
        if (username.Contains("\\"))
            username = username.Split('\\')[1];

        // Check if user is in authorized list
        return AUTHORIZED_USERS.Contains(username, StringComparer.OrdinalIgnoreCase);
    }

    private void ShowAccessDeniedPanel()
    {
        pnlAccessDenied.Visible = true;
        pnlReport.Visible = false;
        lblDeniedUser.Text = User.Identity.IsAuthenticated ? User.Identity.Name : "Anonymous";
    }

    private void ShowReportPanel()
    {
        pnlAccessDenied.Visible = false;
        pnlReport.Visible = true;
        lblCurrentUser.Text = User.Identity.Name;
    }

    protected void btnSendReport_Click(object sender, EventArgs e)
    {
        try
        {
            // Generate email body
            string emailBody = GenerateEmailHTML();
            
            // Create mail message
            MailMessage mail = new MailMessage();
            mail.From = new MailAddress(FROM_EMAIL);
            
            // Add recipients
            string[] recipients = FIXED_RECIPIENTS.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string recipient in recipients)
            {
                mail.To.Add(recipient.Trim());
            }
            
            mail.Subject = "End of day summary " + DateTime.Now.ToString("dd/MM/yy");
            mail.Body = emailBody;
            mail.IsBodyHtml = true;
            
            // Send email
            SmtpClient smtp = new SmtpClient(SMTP_SERVER);
            smtp.Port = 25;
            smtp.EnableSsl = false;
            smtp.UseDefaultCredentials = true; // Use Windows authentication
            smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
            
            // Add logging
            System.Diagnostics.EventLog.WriteEntry("Application", 
                string.Format("Attempting to send email via {0} from {1} to {2}", 
                    SMTP_SERVER, FROM_EMAIL, FIXED_RECIPIENTS), 
                System.Diagnostics.EventLogEntryType.Information);
            
            smtp.Send(mail);
            
            // Show success message
            lblStatus.Text = "Report sent successfully to: Servicedesk24, OPSBridge, and ISSCDL" + 
                            "<br/>SMTP Server: " + SMTP_SERVER + 
                            "<br/>From: " + FROM_EMAIL;
            lblStatus.CssClass = "status-success";
            lblStatus.Visible = true;
            
            // Clear form
            txtOutstandingP1.Text = "";
            txtOutstandingP2.Text = "";
            txtResolvedP1.Text = "";
            txtResolvedP2.Text = "";
        }
        catch (Exception ex)
        {
            // Show error message
            lblStatus.Text = "Error sending report: " + ex.Message;
            lblStatus.CssClass = "status-error";
            lblStatus.Visible = true;
        }
    }

    private string GenerateEmailHTML()
    {
        string outstandingP1 = FormatIncidentDetails(txtOutstandingP1.Text);
        string outstandingP2 = FormatIncidentDetails(txtOutstandingP2.Text);
        string resolvedP1 = FormatIncidentDetails(txtResolvedP1.Text);
        string resolvedP2 = FormatIncidentDetails(txtResolvedP2.Text);
        
        return @"<!DOCTYPE html>
<html>
<head>
    <meta charset='UTF-8'>
    <!--[if mso]>
    <noscript>
        <xml>
            <o:OfficeDocumentSettings>
                <o:PixelsPerInch>96</o:PixelsPerInch>
            </o:OfficeDocumentSettings>
        </xml>
    </noscript>
    <![endif]-->
</head>
<body style='margin:0;padding:0;font-family:""Segoe UI"",Tahoma,Geneva,Verdana,sans-serif;'>
    <table width='100%' cellpadding='0' cellspacing='0' style='background-color:#f4f4f4;'>
        <tr>
            <td align='center' style='padding:20px 0;'>
                <table width='600' cellpadding='0' cellspacing='0' style='background-color:#ffffff;border:1px solid #dddddd;'>
                    <tr>
                        <td style='padding:30px;'>
                            <h2 style='margin:0 0 20px 0;color:#333333;'>End of Day Summary - " + DateTime.Now.ToString("dd/MM/yy") + @"</h2>
                            
                            <p style='margin:15px 0;'><strong style='color:#dc3545;'>Outstanding High Priority P1 Incidents</strong></p>
                            <p style='margin:10px 0 20px 20px;color:#333333;'>" + outstandingP1 + @"</p>
                            
                            <p style='margin:15px 0;'><strong style='color:#fd7e14;'>Outstanding High Priority P2 Incidents</strong></p>
                            <p style='margin:10px 0 20px 20px;color:#333333;'>" + outstandingP2 + @"</p>
                            
                            <p style='margin:15px 0;'><strong style='color:#dc3545;'>Resolved High Priority P1 Incidents</strong></p>
                            <p style='margin:10px 0 20px 20px;color:#333333;'>" + resolvedP1 + @"</p>
                            
                            <p style='margin:15px 0;'><strong style='color:#fd7e14;'>Resolved High Priority P2 Incidents</strong></p>
                            <p style='margin:10px 0 20px 20px;color:#333333;'>" + resolvedP2 + @"</p>
                            
                            <hr style='margin:30px 0;border:none;border-top:1px solid #dddddd;'>
                            <p style='margin:0;color:#666666;font-size:14px;'>For further information, please contact the Service Desk on 02477716787</p>
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
</body>
</html>";
    }

    private string FormatIncidentDetails(string text)
    {
        if (string.IsNullOrWhiteSpace(text))
            return "None to report";
            
        return text.Trim().Replace("\n", "<br>");
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>End of Day Incident Report</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            max-width: 900px;
            margin: 40px auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background: white;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            margin-bottom: 10px;
        }
        .date-info {
            color: #666;
            margin-bottom: 30px;
            font-size: 14px;
        }
        .user-info {
            background: #e3f2fd;
            padding: 10px 15px;
            border-radius: 4px;
            margin-bottom: 20px;
            font-size: 14px;
        }
        .form-group {
            margin-bottom: 25px;
            background: #f9f9f9;
            padding: 20px;
            border-radius: 6px;
            border: 1px solid #e0e0e0;
        }
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #333;
        }
        .priority-badge {
            display: inline-block;
            padding: 3px 8px;
            border-radius: 3px;
            font-size: 12px;
            font-weight: bold;
            margin-left: 5px;
        }
        .p1 {
            background-color: #dc3545;
            color: white;
        }
        .p2 {
            background-color: #fd7e14;
            color: white;
        }
        .textbox {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-family: inherit;
            font-size: 14px;
            min-height: 80px;
            resize: vertical;
        }
        .textbox:focus {
            outline: none;
            border-color: #0066cc;
            box-shadow: 0 0 0 2px rgba(0,102,204,0.2);
        }
        .help-text {
            font-size: 12px;
            color: #666;
            margin-top: 5px;
        }
        .config-section {
            background: #e9ecef;
            padding: 20px;
            border-radius: 6px;
            margin-bottom: 25px;
        }
        .config-section h3 {
            margin-top: 0;
            margin-bottom: 15px;
            font-size: 18px;
        }
        .recipients-input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
        }
        .btn-send {
            background-color: #007bff;
            color: white;
            padding: 12px 30px;
            border: none;
            border-radius: 4px;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
        }
        .btn-send:hover {
            background-color: #0056b3;
        }
        .status-success, .status-error {
            padding: 15px;
            border-radius: 4px;
            margin-top: 20px;
        }
        .status-success {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .status-error {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        .access-denied {
            text-align: center;
            padding: 50px;
        }
        .access-denied h2 {
            color: #dc3545;
            margin-bottom: 20px;
        }
        .access-denied-icon {
            font-size: 72px;
            color: #dc3545;
            margin-bottom: 20px;
        }
        .warning-notice {
            background-color: #fff3cd;
            border: 1px solid #ffeeba;
            border-radius: 4px;
            color: #856404;
            padding: 15px 20px;
            margin-bottom: 25px;
            font-size: 14px;
            line-height: 1.6;
        }
        .warning-notice strong {
            font-size: 16px;
            display: block;
            margin-bottom: 5px;
        }
        .warning-notice a {
            color: #0056b3;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <!-- Access Denied Panel -->
        <asp:Panel ID="pnlAccessDenied" runat="server" Visible="false" CssClass="container">
            <div class="access-denied">
                <div class="access-denied-icon">X</div>
                <h2>Access Denied</h2>
                <p>You are not authorized to access this system.</p>
                <p>Your username: <strong><asp:Label ID="lblDeniedUser" runat="server" /></strong></p>
                <p>Please contact the Service Desk if you require access.</p>
                <p style="margin-top: 30px; color: #666;">Tel: 02477716787</p>
            </div>
        </asp:Panel>

        <!-- Report Panel -->
        <asp:Panel ID="pnlReport" runat="server" Visible="false" CssClass="container">
            <h1>End of Day Incident Report</h1>
            <div class="date-info">Report Date: <asp:Label ID="lblReportDate" runat="server" /></div>
            <div class="user-info">Logged in as: <asp:Label ID="lblCurrentUser" runat="server" /></div>

            <div class="warning-notice">
                <strong>⚠️ PRODUCTION VERSION</strong><br />
                This form will send the report to a large audience including Servicedesk24, OPSBridge, and ISSCDL.<br />
                If you need to send to individuals or test this service, please use the <a href="http://status.stwater.intra/IncidentReport/NonLive-IncidentReport.aspx" target="_blank">test version</a>.
            </div>

            <div class="form-group">
                <label>Outstanding High Priority Incidents <span class="priority-badge p1">P1</span></label>
                <asp:TextBox ID="txtOutstandingP1" runat="server" TextMode="MultiLine" CssClass="textbox" 
                    placeholder="Enter incident details (one per line)&#10;Example: INC0012345 - Database connectivity issue affecting production" />
                <div class="help-text">Leave blank if none to report</div>
            </div>

            <div class="form-group">
                <label>Outstanding High Priority Incidents <span class="priority-badge p2">P2</span></label>
                <asp:TextBox ID="txtOutstandingP2" runat="server" TextMode="MultiLine" CssClass="textbox" 
                    placeholder="Enter incident details (one per line)&#10;Example: INC0012346 - Email delays affecting marketing department" />
                <div class="help-text">Leave blank if none to report</div>
            </div>

            <div class="form-group">
                <label>Resolved High Priority Incidents <span class="priority-badge p1">P1</span></label>
                <asp:TextBox ID="txtResolvedP1" runat="server" TextMode="MultiLine" CssClass="textbox" 
                    placeholder="Enter incident details (one per line)&#10;Example: INC0012347 - Network outage resolved at 14:30" />
                <div class="help-text">Leave blank if none to report</div>
            </div>

            <div class="form-group">
                <label>Resolved High Priority Incidents <span class="priority-badge p2">P2</span></label>
                <asp:TextBox ID="txtResolvedP2" runat="server" TextMode="MultiLine" CssClass="textbox" 
                    placeholder="Enter incident details (one per line)&#10;Example: INC0012348 - Printer issue resolved, driver updated" />
                <div class="help-text">Leave blank if none to report</div>
            </div>

            <div style="text-align: right; margin-top: 30px;">
                <asp:Button ID="btnSendReport" runat="server" Text="Send Report" CssClass="btn-send" OnClick="btnSendReport_Click" />
            </div>

            <asp:Label ID="lblStatus" runat="server" Visible="false" />
        </asp:Panel>
    </form>
</body>
</html>

Installation Instructions

Prerequisites

  • Windows Server 2019 with IIS
  • .NET Framework 4.0 or higher
  • Access to an internal SMTP server

Step 1: Create the Application in IIS

  1. Open IIS Manager
  2. Right-click on your site and select "Add Application"
  3. Set the following:
    • Alias: IncidentReport
    • Application pool: DefaultAppPool (or any pool running .NET 4.0 Integrated)
    • Physical path: C:\inetpub\wwwroot\IncidentReport

Step 2: Configure Authentication

This is critical - the application uses Windows Authentication:

  1. Select your application in IIS
  2. Double-click "Authentication"
  3. Disable Anonymous Authentication
  4. Enable Windows Authentication

Step 3: Configure Application Pool

  1. Select "Application Pools" in IIS
  2. Find your application pool (DefaultAppPool)
  3. Click "Advanced Settings"
  4. Ensure:
    • .NET CLR Version: v4.0
    • Managed Pipeline Mode: Integrated
    • Identity: ApplicationPoolIdentity (or a service account with email permissions)

Step 4: Deploy Files

  1. Copy IncidentReport.aspx to your application folder
  2. Create a minimal web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.web>
        <customErrors mode="Off" />
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
    <system.webServer>
        <defaultDocument>
            <files>
                <add value="IncidentReport.aspx" />
            </files>
        </defaultDocument>
    </system.webServer>
</configuration>

Key Features Explained

Access Control with Windows Authentication

I implemented a simple but effective access control system using Windows Authentication. The key code is in the authorization check:

private readonly string[] AUTHORIZED_USERS = new string[] {
    "user.a",
    "user.b"
};

private bool IsUserAuthorized()
{
    if (!User.Identity.IsAuthenticated)
        return false;

    // Get username without domain
    string username = User.Identity.Name;
    if (username.Contains("\\"))
        username = username.Split('\\')[1];

    // Check if user is in authorized list
    return AUTHORIZED_USERS.Contains(username, StringComparer.OrdinalIgnoreCase);
}

This approach:

  • Uses Windows credentials (no passwords to manage)
  • Strips the domain from usernames for easier management
  • Shows a friendly "Access Denied" page for unauthorized users

The Two-Tier System

The production version includes a warning notice that links to a test version:

<div class="warning-notice">
    <strong>⚠️ PRODUCTION VERSION</strong><br />
    This form will send the report to a large audience including Servicedesk24, OPSBridge, and ISSCDL.<br />
    If you need to send to individuals or test this service, please use the 
    <a href="/NonLive-IncidentReport.aspx" target="_blank">test version</a>.
</div>

The test version (not shown here) includes an editable recipient field, allowing users to:

  • Test the system without spamming production inboxes
  • Send reports to specific individuals when needed
  • Verify formatting before sending to the full distribution list

Smart Handling of Empty Reports

One of the most useful features is how the system handles empty incident fields. The FormatIncidentDetails method automatically converts empty fields to "None to report":

private string FormatIncidentDetails(string text)
{
    if (string.IsNullOrWhiteSpace(text))
        return "None to report";
        
    return text.Trim().Replace("\n", "<br>");
}

This means users can submit the form completely empty on quiet days, and recipients will receive a properly formatted email showing "None to report" for each category.

Email Generation and Sending

The email generation uses inline styles to ensure compatibility with Outlook and OWA:

SmtpClient smtp = new SmtpClient(SMTP_SERVER);
smtp.Port = 25;
smtp.EnableSsl = false;
smtp.UseDefaultCredentials = true; // Uses app pool identity
smtp.DeliveryMethod = SmtpDeliveryMethod.Network;

Lessons Learned

Building this system taught me several valuable lessons:

  1. Keep authentication simple: Windows Authentication eliminated password management
  2. Plan for testing: The two-tier approach saved countless headaches during rollout
  3. Handle edge cases: The "None to report" feature was added after users asked "what if nothing happened?"
  4. Make errors visible: Clear error messages and logging help with troubleshooting

Conclusion

What started as a simple request to "automate our email reports" became a robust system that handles authentication, authorization, form validation, and email delivery. By focusing on the real-world usage patterns and potential failure points, I created a solution that's both powerful and simple to use.

The key to success was understanding that automation isn't just about replacing manual tasks - it's about building systems that prevent errors while remaining flexible enough for real-world use cases. This incident report system now processes dozens of reports daily with zero errors, freeing the Service Desk team to focus on actually resolving incidents rather than reporting on them.