2026-02-21 21:06:36 +00:00
import {
DeesElement ,
customElement ,
html ,
css ,
cssManager ,
property ,
type TemplateResult ,
} from '@design.estate/dees-element' ;
import type { IEmail } from './sz-mta-list-view.js' ;
declare global {
interface HTMLElementTagNameMap {
'sz-mta-detail-view' : SzMtaDetailView ;
}
}
export interface ISmtpLogEntry {
timestamp : string ;
direction : 'client' | 'server' ;
command : string ;
responseCode? : number ;
}
export interface IConnectionInfo {
sourceIp : string ;
sourceHostname : string ;
destinationIp : string ;
destinationPort : number ;
tlsVersion : string ;
tlsCipher : string ;
authenticated : boolean ;
authMethod : string ;
authUser : string ;
}
export interface IAuthenticationResults {
spf : 'pass' | 'fail' | 'softfail' | 'neutral' | 'none' ;
spfDomain : string ;
dkim : 'pass' | 'fail' | 'none' ;
dkimDomain : string ;
dmarc : 'pass' | 'fail' | 'none' ;
dmarcPolicy : string ;
}
export interface IEmailDetail extends IEmail {
to : string ;
toList : string [ ] ;
cc? : string [ ] ;
smtpLog : ISmtpLogEntry [ ] ;
connectionInfo : IConnectionInfo ;
authenticationResults : IAuthenticationResults ;
rejectionReason? : string ;
bounceMessage? : string ;
headers : Record < string , string > ;
body : string ;
}
@customElement ( 'sz-mta-detail-view' )
export class SzMtaDetailView extends DeesElement {
public static demo = ( ) = > html `
< div style = "padding: 24px; max-width: 1200px;" >
< sz - mta - detail - view
. email = $ { {
id : '1' ,
direction : 'outbound' ,
status : 'delivered' ,
from : 'noreply@serve.zone' ,
to : 'user@example.com' ,
toList : [ 'user@example.com' ] ,
subject : 'Welcome to serve.zone' ,
timestamp : '2024-01-15 14:30:22' ,
messageId : '<abc123@serve.zone>' ,
size : '12.4 KB' ,
smtpLog : [
{ timestamp : '14:30:20' , direction : 'client' , command : 'EHLO mail.serve.zone' } ,
{ timestamp : '14:30:20' , direction : 'server' , command : '250-mail.example.com Hello' , responseCode : 250 } ,
{ timestamp : '14:30:20' , direction : 'server' , command : '250-STARTTLS' , responseCode : 250 } ,
{ timestamp : '14:30:20' , direction : 'server' , command : '250 SIZE 52428800' , responseCode : 250 } ,
{ timestamp : '14:30:20' , direction : 'client' , command : 'STARTTLS' } ,
{ timestamp : '14:30:21' , direction : 'server' , command : '220 Ready to start TLS' , responseCode : 220 } ,
{ timestamp : '14:30:21' , direction : 'client' , command : 'EHLO mail.serve.zone' } ,
{ timestamp : '14:30:21' , direction : 'server' , command : '250-mail.example.com Hello' , responseCode : 250 } ,
{ timestamp : '14:30:21' , direction : 'server' , command : '250-AUTH PLAIN LOGIN' , responseCode : 250 } ,
{ timestamp : '14:30:21' , direction : 'server' , command : '250 SIZE 52428800' , responseCode : 250 } ,
{ timestamp : '14:30:21' , direction : 'client' , command : 'AUTH PLAIN AHVzZXIAcGFzc3dvcmQ=' } ,
{ timestamp : '14:30:21' , direction : 'server' , command : '235 2.7.0 Authentication successful' , responseCode : 235 } ,
{ timestamp : '14:30:21' , direction : 'client' , command : 'MAIL FROM:<noreply@serve.zone>' } ,
{ timestamp : '14:30:21' , direction : 'server' , command : '250 OK' , responseCode : 250 } ,
{ timestamp : '14:30:21' , direction : 'client' , command : 'RCPT TO:<user@example.com>' } ,
{ timestamp : '14:30:21' , direction : 'server' , command : '250 Accepted' , responseCode : 250 } ,
{ timestamp : '14:30:22' , direction : 'client' , command : 'DATA' } ,
{ timestamp : '14:30:22' , direction : 'server' , command : '354 Enter message, ending with "." on a line by itself' , responseCode : 354 } ,
{ timestamp : '14:30:22' , direction : 'client' , command : '.' } ,
{ timestamp : '14:30:22' , direction : 'server' , command : '250 OK id=1pQ2rS-0003Ab-C4' , responseCode : 250 } ,
{ timestamp : '14:30:22' , direction : 'client' , command : 'QUIT' } ,
{ timestamp : '14:30:22' , direction : 'server' , command : '221 mail.example.com closing connection' , responseCode : 221 } ,
] ,
connectionInfo : {
sourceIp : '10.0.1.50' ,
sourceHostname : 'mail.serve.zone' ,
destinationIp : '93.184.216.34' ,
destinationPort : 25 ,
tlsVersion : 'TLSv1.3' ,
tlsCipher : 'TLS_AES_256_GCM_SHA384' ,
authenticated : true ,
authMethod : 'PLAIN' ,
authUser : 'noreply@serve.zone' ,
} ,
authenticationResults : {
spf : 'pass' ,
spfDomain : 'serve.zone' ,
dkim : 'pass' ,
dkimDomain : 'serve.zone' ,
dmarc : 'pass' ,
dmarcPolicy : 'reject' ,
} ,
headers : {
'From' : 'noreply@serve.zone' ,
'To' : 'user@example.com' ,
'Subject' : 'Welcome to serve.zone' ,
'Date' : 'Mon, 15 Jan 2024 14:30:22 +0000' ,
'MIME-Version' : '1.0' ,
'Content-Type' : 'text/html; charset=UTF-8' ,
} ,
body : '<html>\\n<head><title>Welcome</title></head>\\n<body>\\n <h1>Welcome to serve.zone!</h1>\\n <p>Your account has been created successfully.</p>\\n <a href="https://serve.zone/dashboard">Go to Dashboard</a>\\n</body>\\n</html>' ,
} }
> < / s z - m t a - d e t a i l - v i e w >
< / div >
` ;
public static demoGroups = [ 'MTA' ] ;
@property ( { type : Object } )
public accessor email : IEmailDetail | null = null ;
public static styles = [
cssManager . defaultStyles ,
css `
: host {
display : block ;
}
. header {
display : flex ;
align - items : center ;
gap : 16px ;
margin - bottom : 24px ;
}
. back - link {
display : inline - flex ;
align - items : center ;
gap : 6px ;
font - size : 14px ;
color : $ { cssManager . bdTheme ( '#71717a' , '#a1a1aa' ) } ;
cursor : pointer ;
transition : color 200 ms ease ;
}
. back - link :hover {
color : $ { cssManager . bdTheme ( '#18181b' , '#fafafa' ) } ;
}
. email - header {
display : flex ;
align - items : center ;
gap : 12px ;
margin - bottom : 24px ;
}
. email - subject {
font - size : 24px ;
font - weight : 700 ;
color : $ { cssManager . bdTheme ( '#18181b' , '#fafafa' ) } ;
margin : 0 ;
}
. badge - group {
display : flex ;
gap : 8px ;
}
. status - badge {
display : inline - flex ;
align - items : center ;
padding : 4px 12 px ;
border - radius : 9999px ;
font - size : 13px ;
font - weight : 500 ;
}
. status - badge . delivered {
background : $ { cssManager . bdTheme ( '#dcfce7' , 'rgba(34, 197, 94, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#16a34a' , '#22c55e' ) } ;
}
. status - badge . bounced ,
. status - badge . rejected {
background : $ { cssManager . bdTheme ( '#fee2e2' , 'rgba(239, 68, 68, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#dc2626' , '#ef4444' ) } ;
}
. status - badge . deferred {
background : $ { cssManager . bdTheme ( '#fef9c3' , 'rgba(250, 204, 21, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#ca8a04' , '#facc15' ) } ;
}
. status - badge . pending {
background : $ { cssManager . bdTheme ( '#dbeafe' , 'rgba(59, 130, 246, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#2563eb' , '#60a5fa' ) } ;
}
. direction - badge {
display : inline - flex ;
align - items : center ;
gap : 4px ;
padding : 4px 12 px ;
border - radius : 9999px ;
font - size : 13px ;
font - weight : 500 ;
}
. direction - badge . inbound {
background : $ { cssManager . bdTheme ( '#dcfce7' , 'rgba(34, 197, 94, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#16a34a' , '#22c55e' ) } ;
}
. direction - badge . outbound {
background : $ { cssManager . bdTheme ( '#dbeafe' , 'rgba(59, 130, 246, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#2563eb' , '#60a5fa' ) } ;
}
. content {
display : grid ;
grid - template - columns : 1fr ;
gap : 24px ;
}
@media ( min - width : 1024px ) {
. content {
grid - template - columns : 2fr 1 fr ;
}
}
. main - content {
display : flex ;
flex - direction : column ;
gap : 24px ;
}
. sidebar {
display : flex ;
flex - direction : column ;
gap : 24px ;
}
2026-04-07 22:27:23 +00:00
. card - header {
height : 36px ;
display : flex ;
align - items : center ;
padding : 0 16 px ;
width : 100 % ;
box - sizing : border - box ;
}
. card - heading {
flex : 1 ;
display : flex ;
align - items : baseline ;
gap : 8px ;
min - width : 0 ;
}
. card - title {
font - weight : 500 ;
font - size : 13px ;
letter - spacing : - 0.01 em ;
color : var ( -- dees - color - text - secondary ) ;
white - space : nowrap ;
2026-02-21 21:06:36 +00:00
overflow : hidden ;
2026-04-07 22:27:23 +00:00
text - overflow : ellipsis ;
2026-02-21 21:06:36 +00:00
}
2026-04-07 22:27:23 +00:00
. card - subtitle {
font - size : 12px ;
color : var ( -- dees - color - text - muted ) ;
letter - spacing : - 0.01 em ;
white - space : nowrap ;
overflow : hidden ;
text - overflow : ellipsis ;
}
. card - footer {
2026-02-21 21:06:36 +00:00
display : flex ;
2026-04-07 22:27:23 +00:00
flex - direction : row ;
justify - content : flex - end ;
2026-02-21 21:06:36 +00:00
align - items : center ;
2026-04-07 22:27:23 +00:00
gap : 0 ;
height : 36px ;
width : 100 % ;
box - sizing : border - box ;
2026-02-21 21:06:36 +00:00
}
2026-04-07 22:27:23 +00:00
. tile - button {
padding : 0 16 px ;
height : 100 % ;
text - align : center ;
font - size : 12px ;
font - weight : 500 ;
cursor : pointer ;
user - select : none ;
transition : all 0.15 s ease ;
background : transparent ;
border : none ;
border - left : 1px solid var ( -- dees - color - border - subtle ) ;
color : var ( -- dees - color - text - muted ) ;
white - space : nowrap ;
display : flex ;
align - items : center ;
gap : 6px ;
}
. tile - button :first - child {
border - left : none ;
}
. tile - button :hover {
background : var ( -- dees - color - hover ) ;
color : var ( -- dees - color - text - primary ) ;
}
. tile - button . primary {
color : $ { cssManager . bdTheme ( 'hsl(217.2 91.2% 59.8%)' , 'hsl(213.1 93.9% 67.8%)' ) } ;
2026-02-21 21:06:36 +00:00
font - weight : 600 ;
}
2026-04-07 22:27:23 +00:00
. tile - button.primary :hover {
background : $ { cssManager . bdTheme ( 'hsl(217.2 91.2% 59.8% / 0.08)' , 'hsl(213.1 93.9% 67.8% / 0.08)' ) } ;
color : $ { cssManager . bdTheme ( 'hsl(217.2 91.2% 50%)' , 'hsl(213.1 93.9% 75%)' ) } ;
2026-02-21 21:06:36 +00:00
}
. card - content {
padding : 16px ;
}
. detail - list {
display : flex ;
flex - direction : column ;
gap : 12px ;
}
. detail - item {
display : flex ;
justify - content : space - between ;
align - items : flex - start ;
}
. detail - label {
font - size : 14px ;
color : $ { cssManager . bdTheme ( '#71717a' , '#a1a1aa' ) } ;
flex - shrink : 0 ;
}
. detail - value {
font - size : 14px ;
color : $ { cssManager . bdTheme ( '#18181b' , '#fafafa' ) } ;
text - align : right ;
word - break : break - all ;
}
. smtp - log - container {
padding : 16px ;
font - family : 'SF Mono' , Monaco , 'Cascadia Code' , monospace ;
font - size : 13px ;
line - height : 1.6 ;
max - height : 500px ;
overflow - y : auto ;
background : $ { cssManager . bdTheme ( '#fafafa' , '#0a0a0a' ) } ;
display : flex ;
flex - direction : column ;
gap : 8px ;
scrollbar - width : thin ;
scrollbar - color : $ { cssManager . bdTheme ( '#d4d4d8' , '#3f3f46' ) } transparent ;
}
. smtp - log - container : : - webkit - scrollbar {
width : 6px ;
}
. smtp - log - container : : - webkit - scrollbar - track {
background : transparent ;
}
. smtp - log - container : : - webkit - scrollbar - thumb {
background : $ { cssManager . bdTheme ( '#d4d4d8' , '#3f3f46' ) } ;
border - radius : 3px ;
}
/* Phase separators */
. smtp - phase - separator {
display : flex ;
align - items : center ;
gap : 12px ;
padding : 4px 0 ;
margin : 4px 0 ;
}
. smtp - phase - line {
flex : 1 ;
height : 1px ;
background : $ { cssManager . bdTheme ( '#e4e4e7' , '#27272a' ) } ;
}
. smtp - phase - label {
font - size : 10px ;
font - weight : 600 ;
text - transform : uppercase ;
letter - spacing : 0.5px ;
color : $ { cssManager . bdTheme ( '#a1a1aa' , '#52525b' ) } ;
white - space : nowrap ;
}
/* Chat bubbles */
. smtp - bubble {
border - radius : 8px ;
padding : 10px 14 px ;
max - width : 70 % ;
}
. smtp - bubble . client {
align - self : flex - start ;
background : $ { cssManager . bdTheme ( 'rgba(59, 130, 246, 0.08)' , 'rgba(59, 130, 246, 0.12)' ) } ;
border - left : 3px solid $ { cssManager . bdTheme ( '#3b82f6' , '#60a5fa' ) } ;
margin - right : auto ;
}
. smtp - bubble . server {
align - self : flex - end ;
background : $ { cssManager . bdTheme ( 'rgba(34, 197, 94, 0.06)' , 'rgba(34, 197, 94, 0.10)' ) } ;
border - right : 3px solid $ { cssManager . bdTheme ( '#22c55e' , '#4ade80' ) } ;
margin - left : auto ;
text - align : right ;
}
. smtp - bubble - command {
white - space : pre - wrap ;
word - break : break - all ;
color : $ { cssManager . bdTheme ( '#18181b' , '#fafafa' ) } ;
}
. smtp - bubble - meta {
display : flex ;
align - items : center ;
gap : 6px ;
margin - top : 4px ;
font - size : 11px ;
color : $ { cssManager . bdTheme ( '#a1a1aa' , '#52525b' ) } ;
}
. smtp - bubble . server . smtp - bubble - meta {
justify - content : flex - end ;
}
. smtp - direction - tag {
font - size : 10px ;
font - weight : 600 ;
text - transform : uppercase ;
letter - spacing : 0.3px ;
}
. smtp - direction - tag . client {
color : $ { cssManager . bdTheme ( '#3b82f6' , '#60a5fa' ) } ;
}
. smtp - direction - tag . server {
color : $ { cssManager . bdTheme ( '#22c55e' , '#4ade80' ) } ;
}
/* Response code badges */
. response - code - badge {
display : inline - block ;
padding : 1px 7 px ;
border - radius : 9999px ;
font - size : 11px ;
font - weight : 700 ;
margin - bottom : 4px ;
font - family : 'SF Mono' , Monaco , 'Cascadia Code' , monospace ;
}
. response - code - badge . code - 2 xx {
background : $ { cssManager . bdTheme ( 'rgba(34, 197, 94, 0.15)' , 'rgba(34, 197, 94, 0.25)' ) } ;
color : $ { cssManager . bdTheme ( '#16a34a' , '#22c55e' ) } ;
}
. response - code - badge . code - 3 xx {
background : $ { cssManager . bdTheme ( 'rgba(59, 130, 246, 0.15)' , 'rgba(59, 130, 246, 0.25)' ) } ;
color : $ { cssManager . bdTheme ( '#2563eb' , '#60a5fa' ) } ;
}
. response - code - badge . code - 4 xx {
background : $ { cssManager . bdTheme ( 'rgba(250, 204, 21, 0.15)' , 'rgba(250, 204, 21, 0.25)' ) } ;
color : $ { cssManager . bdTheme ( '#ca8a04' , '#facc15' ) } ;
}
. response - code - badge . code - 5 xx {
background : $ { cssManager . bdTheme ( 'rgba(239, 68, 68, 0.15)' , 'rgba(239, 68, 68, 0.25)' ) } ;
color : $ { cssManager . bdTheme ( '#dc2626' , '#ef4444' ) } ;
}
2026-04-07 22:27:23 +00:00
/* SMTP metadata banner — sits inside content, above the log */
2026-02-21 21:06:36 +00:00
. smtp - header - subtitle {
2026-04-07 22:27:23 +00:00
padding : 10px 16 px ;
font - size : 12px ;
color : var ( -- dees - color - text - muted ) ;
border - bottom : 1px solid var ( -- dees - color - border - subtle ) ;
font - family : monospace ;
2026-02-21 21:06:36 +00:00
}
. smtp - direction - badge {
display : inline - flex ;
align - items : center ;
padding : 2px 8 px ;
border - radius : 9999px ;
font - size : 11px ;
font - weight : 600 ;
text - transform : uppercase ;
letter - spacing : 0.3px ;
}
. smtp - direction - badge . inbound {
background : $ { cssManager . bdTheme ( 'rgba(34, 197, 94, 0.12)' , 'rgba(34, 197, 94, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#16a34a' , '#22c55e' ) } ;
}
. smtp - direction - badge . outbound {
background : $ { cssManager . bdTheme ( 'rgba(59, 130, 246, 0.12)' , 'rgba(59, 130, 246, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#2563eb' , '#60a5fa' ) } ;
}
. email - body - container {
padding : 16px ;
font - family : monospace ;
font - size : 13px ;
max - height : 500px ;
overflow - y : auto ;
background : $ { cssManager . bdTheme ( '#fafafa' , '#0a0a0a' ) } ;
white - space : pre - wrap ;
word - break : break - all ;
color : $ { cssManager . bdTheme ( '#18181b' , '#fafafa' ) } ;
}
. tls - badge {
display : inline - flex ;
align - items : center ;
padding : 2px 8 px ;
border - radius : 4px ;
font - size : 12px ;
font - weight : 500 ;
background : $ { cssManager . bdTheme ( '#dcfce7' , 'rgba(34, 197, 94, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#16a34a' , '#22c55e' ) } ;
}
. auth - row {
display : flex ;
justify - content : space - between ;
align - items : center ;
padding : 8px 0 ;
border - bottom : 1px solid $ { cssManager . bdTheme ( '#f4f4f5' , '#27272a' ) } ;
}
. auth - row :last - child {
border - bottom : none ;
}
. auth - label {
font - size : 14px ;
font - weight : 500 ;
color : $ { cssManager . bdTheme ( '#18181b' , '#fafafa' ) } ;
}
. auth - domain {
font - size : 12px ;
color : $ { cssManager . bdTheme ( '#71717a' , '#a1a1aa' ) } ;
margin - left : 8px ;
}
. auth - badge {
display : inline - flex ;
align - items : center ;
padding : 2px 8 px ;
border - radius : 9999px ;
font - size : 12px ;
font - weight : 500 ;
}
. auth - badge . pass {
background : $ { cssManager . bdTheme ( '#dcfce7' , 'rgba(34, 197, 94, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#16a34a' , '#22c55e' ) } ;
}
. auth - badge . fail {
background : $ { cssManager . bdTheme ( '#fee2e2' , 'rgba(239, 68, 68, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#dc2626' , '#ef4444' ) } ;
}
. auth - badge . softfail ,
. auth - badge . neutral ,
. auth - badge . none {
background : $ { cssManager . bdTheme ( '#fef9c3' , 'rgba(250, 204, 21, 0.2)' ) } ;
color : $ { cssManager . bdTheme ( '#ca8a04' , '#facc15' ) } ;
}
2026-04-07 22:27:23 +00:00
dees - tile . rejection - card : : part ( outer ) {
2026-02-21 21:06:36 +00:00
border - color : $ { cssManager . bdTheme ( '#fecaca' , 'rgba(239, 68, 68, 0.3)' ) } ;
}
. rejection - content {
font - size : 14px ;
color : $ { cssManager . bdTheme ( '#dc2626' , '#ef4444' ) } ;
}
. rejection - label {
font - size : 12px ;
font - weight : 600 ;
text - transform : uppercase ;
letter - spacing : 0.05em ;
color : $ { cssManager . bdTheme ( '#71717a' , '#a1a1aa' ) } ;
margin - bottom : 4px ;
}
. rejection - text {
font - family : monospace ;
font - size : 13px ;
padding : 8px 12 px ;
background : $ { cssManager . bdTheme ( '#fef2f2' , 'rgba(239, 68, 68, 0.1)' ) } ;
border - radius : 4px ;
margin - bottom : 12px ;
color : $ { cssManager . bdTheme ( '#991b1b' , '#fca5a5' ) } ;
}
. rejection - text :last - child {
margin - bottom : 0 ;
}
. no - email {
padding : 48px 24 px ;
text - align : center ;
color : $ { cssManager . bdTheme ( '#71717a' , '#a1a1aa' ) } ;
}
` ,
] ;
public render ( ) : TemplateResult {
if ( ! this . email ) {
return html ` <div class="no-email">No email selected</div> ` ;
}
const email = this . email ;
return html `
< div class = "header" >
< div class = "back-link" @ click = $ { ( ) = > this . handleBack ( ) } >
< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >
< polyline points = "15 18 9 12 15 6" > < / polyline >
< / svg >
Back to Emails
< / div >
< / div >
< div class = "email-header" >
< h1 class = "email-subject" > $ { email . subject } < / h1 >
< div class = "badge-group" >
< span class = "status-badge ${email.status}" > $ { email . status } < / span >
< span class = "direction-badge ${email.direction}" > $ { email . direction } < / span >
< / div >
< / div >
< div class = "content" >
< div class = "main-content" >
<!-- Email Metadata -->
2026-04-07 22:27:23 +00:00
< dees - tile >
< div slot = "header" class = "card-header" >
< div class = "card-heading" >
< span class = "card-title" > Email Metadata < / span >
< / div >
2026-02-21 21:06:36 +00:00
< / div >
< div class = "card-content" >
< div class = "detail-list" >
< div class = "detail-item" >
< span class = "detail-label" > From < / span >
< span class = "detail-value" > $ { email . from } < / span >
< / div >
< div class = "detail-item" >
< span class = "detail-label" > To < / span >
< span class = "detail-value" > $ { email . toList . join ( ', ' ) } < / span >
< / div >
$ { email . cc && email . cc . length > 0 ? html `
< div class = "detail-item" >
< span class = "detail-label" > CC < / span >
< span class = "detail-value" > $ { email . cc . join ( ', ' ) } < / span >
< / div >
` : ''}
< div class = "detail-item" >
< span class = "detail-label" > Subject < / span >
< span class = "detail-value" > $ { email . subject } < / span >
< / div >
< div class = "detail-item" >
< span class = "detail-label" > Date < / span >
< span class = "detail-value" > $ { email . timestamp } < / span >
< / div >
< div class = "detail-item" >
< span class = "detail-label" > Message ID < / span >
< span class = "detail-value" > $ { email . messageId } < / span >
< / div >
< div class = "detail-item" >
< span class = "detail-label" > Size < / span >
< span class = "detail-value" > $ { email . size } < / span >
< / div >
< / div >
< / div >
2026-04-07 22:27:23 +00:00
< / d e e s - t i l e >
2026-02-21 21:06:36 +00:00
<!-- SMTP Transaction Log -->
2026-04-07 22:27:23 +00:00
< dees - tile >
< div slot = "header" class = "card-header" >
< div class = "card-heading" >
< span class = "card-title" > SMTP Transaction Log < / span >
< span class = "smtp-direction-badge ${email.direction}" > $ { email . direction } < / span >
2026-02-21 21:06:36 +00:00
< / div >
2026-04-07 22:27:23 +00:00
< / div >
< div class = "smtp-header-subtitle" >
$ { email . direction === 'outbound'
? ` ${ email . connectionInfo . sourceHostname } → ${ email . connectionInfo . destinationIp } : ${ email . connectionInfo . destinationPort } `
: ` ${ email . connectionInfo . sourceIp } → ${ email . connectionInfo . sourceHostname } : ${ email . connectionInfo . destinationPort } `
}
< / div >
$ { this . renderSmtpLog ( email ) }
< div slot = "footer" class = "card-footer" >
< button class = "tile-button" @ click = $ { ( ) = > this . copySmtpLog ( ) } >
< svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
2026-02-21 21:06:36 +00:00
< rect x = "9" y = "9" width = "13" height = "13" rx = "2" ry = "2" > < / rect >
< path d = "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" > < / path >
< / svg >
Copy Log
< / button >
< / div >
2026-04-07 22:27:23 +00:00
< / d e e s - t i l e >
2026-02-21 21:06:36 +00:00
<!-- Email Body -->
2026-04-07 22:27:23 +00:00
< dees - tile >
< div slot = "header" class = "card-header" >
< div class = "card-heading" >
< span class = "card-title" > Email Body ( Escaped ) < / span >
< span class = "card-subtitle" > Raw content — HTML is not rendered < / span >
2026-02-21 21:06:36 +00:00
< / div >
< / div >
< pre class = "email-body-container" > $ { email . body } < / pre >
2026-04-07 22:27:23 +00:00
< / d e e s - t i l e >
2026-02-21 21:06:36 +00:00
< / div >
< div class = "sidebar" >
<!-- Connection Info -->
2026-04-07 22:27:23 +00:00
< dees - tile >
< div slot = "header" class = "card-header" >
< div class = "card-heading" >
< span class = "card-title" > Connection Info < / span >
< / div >
2026-02-21 21:06:36 +00:00
< / div >
< div class = "card-content" >
< div class = "detail-list" >
< div class = "detail-item" >
< span class = "detail-label" > Source IP < / span >
< span class = "detail-value" > $ { email . connectionInfo . sourceIp } < / span >
< / div >
< div class = "detail-item" >
< span class = "detail-label" > Source Hostname < / span >
< span class = "detail-value" > $ { email . connectionInfo . sourceHostname } < / span >
< / div >
< div class = "detail-item" >
< span class = "detail-label" > Destination < / span >
< span class = "detail-value" > $ { email . connectionInfo . destinationIp } : $ { email . connectionInfo . destinationPort } < / span >
< / div >
< div class = "detail-item" >
< span class = "detail-label" > TLS < / span >
< span class = "detail-value" >
$ { email . connectionInfo . tlsVersion
? html ` <span class="tls-badge"> ${ email . connectionInfo . tlsVersion } </span> `
: 'None' }
< / span >
< / div >
$ { email . connectionInfo . tlsCipher ? html `
< div class = "detail-item" >
< span class = "detail-label" > Cipher < / span >
< span class = "detail-value" > $ { email . connectionInfo . tlsCipher } < / span >
< / div >
` : ''}
< div class = "detail-item" >
< span class = "detail-label" > Authenticated < / span >
< span class = "detail-value" > $ { email . connectionInfo . authenticated ? 'Yes' : 'No' } < / span >
< / div >
$ { email . connectionInfo . authenticated ? html `
< div class = "detail-item" >
< span class = "detail-label" > Auth Method < / span >
< span class = "detail-value" > $ { email . connectionInfo . authMethod } < / span >
< / div >
< div class = "detail-item" >
< span class = "detail-label" > Auth User < / span >
< span class = "detail-value" > $ { email . connectionInfo . authUser } < / span >
< / div >
` : ''}
< / div >
< / div >
2026-04-07 22:27:23 +00:00
< / d e e s - t i l e >
2026-02-21 21:06:36 +00:00
<!-- Authentication Results -->
2026-04-07 22:27:23 +00:00
< dees - tile >
< div slot = "header" class = "card-header" >
< div class = "card-heading" >
< span class = "card-title" > Authentication Results < / span >
< / div >
2026-02-21 21:06:36 +00:00
< / div >
< div class = "card-content" >
< div class = "auth-row" >
< div >
< span class = "auth-label" > SPF < / span >
< span class = "auth-domain" > $ { email . authenticationResults . spfDomain } < / span >
< / div >
< span class = "auth-badge ${email.authenticationResults.spf}" > $ { email . authenticationResults . spf } < / span >
< / div >
< div class = "auth-row" >
< div >
< span class = "auth-label" > DKIM < / span >
< span class = "auth-domain" > $ { email . authenticationResults . dkimDomain } < / span >
< / div >
< span class = "auth-badge ${email.authenticationResults.dkim}" > $ { email . authenticationResults . dkim } < / span >
< / div >
< div class = "auth-row" >
< div >
< span class = "auth-label" > DMARC < / span >
< span class = "auth-domain" > policy : $ { email . authenticationResults . dmarcPolicy } < / span >
< / div >
< span class = "auth-badge ${email.authenticationResults.dmarc}" > $ { email . authenticationResults . dmarc } < / span >
< / div >
< / div >
2026-04-07 22:27:23 +00:00
< / d e e s - t i l e >
2026-02-21 21:06:36 +00:00
<!-- Rejection Details (conditional) -->
$ { email . status === 'rejected' || email . status === 'bounced' ? html `
2026-04-07 22:27:23 +00:00
< dees - tile class = "rejection-card" >
< div slot = "header" class = "card-header" >
< div class = "card-heading" >
< span class = "card-title" > Rejection Details < / span >
< / div >
2026-02-21 21:06:36 +00:00
< / div >
< div class = "card-content" >
$ { email . rejectionReason ? html `
< div class = "rejection-label" > Rejection Reason < / div >
< div class = "rejection-text" > $ { email . rejectionReason } < / div >
` : ''}
$ { email . bounceMessage ? html `
< div class = "rejection-label" > Bounce Message < / div >
< div class = "rejection-text" > $ { email . bounceMessage } < / div >
` : ''}
< / div >
2026-04-07 22:27:23 +00:00
< / d e e s - t i l e >
2026-02-21 21:06:36 +00:00
` : ''}
< / div >
< / div >
` ;
}
private getResponseCodeBadgeClass ( code : number ) : string {
if ( code >= 500 ) return 'code-5xx' ;
if ( code >= 400 ) return 'code-4xx' ;
if ( code >= 300 ) return 'code-3xx' ;
return 'code-2xx' ;
}
private getSmtpPhases ( log : ISmtpLogEntry [ ] ) : Array < { phase : string ; label : string ; entries : ISmtpLogEntry [ ] } > {
const phases : Array < { phase : string ; label : string ; entries : ISmtpLogEntry [ ] } > = [ ] ;
let currentPhase = '' ;
let ehloCount = 0 ;
for ( const entry of log ) {
const cmd = entry . command . toUpperCase ( ) ;
let phase = currentPhase ;
if ( entry . direction === 'client' ) {
if ( cmd . startsWith ( 'EHLO' ) || cmd . startsWith ( 'HELO' ) ) {
ehloCount ++ ;
if ( ehloCount === 1 ) {
phase = 'connection' ;
} else {
phase = 'post-tls' ;
}
} else if ( cmd === 'STARTTLS' ) {
phase = 'tls' ;
} else if ( cmd . startsWith ( 'AUTH' ) ) {
phase = 'auth' ;
} else if ( cmd . startsWith ( 'MAIL FROM' ) || cmd . startsWith ( 'RCPT TO' ) || cmd === 'DATA' || cmd === '.' ) {
phase = 'transfer' ;
} else if ( cmd === 'QUIT' ) {
phase = 'closing' ;
}
}
// Server responses stay in the current phase
if ( entry . direction === 'server' && phase === '' ) {
phase = currentPhase || 'connection' ;
}
if ( phase === '' ) phase = 'connection' ;
if ( phase !== currentPhase ) {
currentPhase = phase ;
const labels : Record < string , string > = {
'connection' : 'Connection' ,
'tls' : 'TLS Negotiation' ,
'post-tls' : 'Post-TLS Handshake' ,
'auth' : 'Authentication' ,
'transfer' : 'Mail Transfer' ,
'closing' : 'Closing' ,
} ;
phases . push ( { phase , label : labels [ phase ] || phase , entries : [ ] } ) ;
}
if ( phases . length === 0 ) {
phases . push ( { phase : 'connection' , label : 'Connection' , entries : [ ] } ) ;
}
phases [ phases . length - 1 ] . entries . push ( entry ) ;
}
return phases ;
}
private renderSmtpLog ( email : IEmailDetail ) : TemplateResult {
const phases = this . getSmtpPhases ( email . smtpLog ) ;
return html `
< div class = "smtp-log-container" >
$ { phases . map ( phase = > html `
< div class = "smtp-phase-separator" >
< div class = "smtp-phase-line" > < / div >
< span class = "smtp-phase-label" > $ { phase . label } < / span >
< div class = "smtp-phase-line" > < / div >
< / div >
$ { phase . entries . map ( entry = > html `
< div class = "smtp-bubble ${entry.direction}" >
$ { entry . direction === 'server' && entry . responseCode ? html `
< span class = "response-code-badge ${this.getResponseCodeBadgeClass(entry.responseCode)}" > $ { entry . responseCode } < / span >
` : ''}
< div class = "smtp-bubble-command" > $ { entry . command } < / div >
< div class = "smtp-bubble-meta" >
< span > $ { entry . timestamp } < / span >
< span > · < / span >
< span class = "smtp-direction-tag ${entry.direction}" > $ { entry . direction === 'client' ? 'Client' : 'Server' } < / span >
< / div >
< / div >
` )}
` )}
< / div >
` ;
}
private copySmtpLog() {
if ( ! this . email ) return ;
const text = this . email . smtpLog
. map ( e = > ` [ ${ e . timestamp } ] ${ e . direction === 'client' ? 'C:' : 'S:' } ${ e . command } ` )
. join ( '\n' ) ;
navigator . clipboard . writeText ( text ) ;
}
private handleBack() {
this . dispatchEvent ( new CustomEvent ( 'back' , { bubbles : true , composed : true } ) ) ;
}
}