Last Updated: April 21, 2026
Analysis Scope: Comprehensive code review of all major GoMail features
✅ YES - Fully implemented
- Location: smtp/session.go
handleEHLO()(line 125),handleHELO()(line 178) - Details: Advertises capabilities including SIZE, 8BITMIME, ENHANCEDSTATUSCODES, PIPELINING, DSN, SMTPUTF8, CHUNKING, and STARTTLS (if TLS available)
- Status: Production ready
✅ YES - Fully implemented
- Location: smtp/session.go
- Details: Advertised in EHLO response and supported by parser
- Status: Production ready
✅ YES - Fully implemented
- Location: smtp/session.go (advertised), smtp/session.go
handleMAIL()(parameter detection) - Details: UTF8 parameter checked in MAIL FROM command, stored in session
- Status: Production ready
✅ YES - Fully implemented
- Location: smtp/session.go
handleBDAT()function - Details: Full BDAT command implementation with chunk size validation and LAST qualifier handling
- Size enforcement: SIZE limits enforced per chunk (line 370)
- Status: Production ready
✅ YES - Advertised, partial buffering
- Location: smtp/session.go (advertised in EHLO)
- Details: Advertised to clients; command parsing loop handles sequential commands
- Limitation: Commands processed sequentially (line 108-130), not truly buffered for pipelining
- Status: Advertised but clients should not rely on full pipelining support
✅ YES - Fully implemented
- Location: smtp/session.go (advertised), smtp/session.go MAIL handling
- Details:
- MAIL FROM: Parses DSN RET, ENVID parameters →
parseDSNMailParams() - RCPT TO: Parses NOTIFY, ORCPT parameters →
parseDSNRcptParams() - Delivery reports generated on failure (see Delivery/Queue section)
- MAIL FROM: Parses DSN RET, ENVID parameters →
- Status: Production ready
✅ YES - Fully implemented with enforcement
- Location: smtp/session.go (advertised), smtp/session.go and smtp/session.go (enforced)
- Details:
- Advertised limit in EHLO response based on config (
maxSize) - DATA command enforces size limit during message reception
- BDAT enforces size limit per chunk
- Advertised limit in EHLO response based on config (
- Error: 552 "Message exceeds maximum size"
- Status: Production ready
✅ YES - Fully implemented for inbound and outbound
- Inbound: smtp/session.go
handleSTARTTLS()- Advertised in EHLO if TLS config provided
- TLS handshake performed, connection upgraded
- Outbound: smtp/outbound.go in
deliverToHost()- STARTTLS attempted if supported by server
- Can be made mandatory via MTA-STS policy
- Status: Production ready
✅ YES - Fully implemented
- Location: auth/dkim.go (signing), delivery/queue.go (applied)
- Key Features:
- Per-domain DKIM signers loaded from database
- Algorithms supported: RSA-2048, Ed25519
- Header canonicalization: relaxed
- Public key generation and storage in DB
- Body hash computed per RFC 6376 §3.7
- Signing location: delivery/queue.go calls
signer.Sign() - Database: DKIM keys stored in
domainstable (dkim_selector, dkim_algorithm, dkim_private_key, dkim_public_key) - Status: Production ready
✅ YES - Fully implemented
- Location: auth/dkim.go
VerifyDKIM()(line 230), auth/dkim_lookup.goLookupDKIMPublicKey() - Key Features:
- DNS lookup for selector._domainkey.domain TXT records
- Signature verification with RSA-2048 and Ed25519
- Body hash verification
- Header list validation
- Proper error handling (temperror vs permerror)
- Called from: smtp/inbound.go during message processing
- Status: Production ready
✅ YES - Fully RF-compliant implementation
- Location: auth/spf.go
CheckSPF()(line 26) - Mechanisms Supported:
- ✅
all- Universal match (line 182) - ✅
a- A record lookup with optional CIDR (line 187) - ✅
mx- MX record lookup with optional CIDR (line 191) - ✅
ip4/ip6- IP4 and IP6 CIDR blocks (line 196, 199) - ✅
ptr- Reverse DNS validation (deprecated but implemented, line 221) - ✅
exists- Macro expansion DNS query (line 201) - ✅
include- Recursive include (RFC 7208 §5.2, line 169) - ✅
redirect- Redirect mechanism (line 167-169)
- ✅
- Modifiers Supported:
- ✅
exp=- Explanation text fetching (line 192 in results) - ✅ Macro expansion (RFC 7208 §7)
- ✅
- RFC Compliance:
- DNS lookup limit: 10 (enforced line 79)
- Void lookup limit: 2 (RFC 7208 §4.6.4)
- Depth tracking for includes/redirects
- NXDOMAIN handling per RFC
- Status: Production ready
✅ YES - Fully compliant implementation
- Location: auth/dmarc.go
CheckDMARC() - Policy Evaluation:
- ✅ Policy lookup (
lookupDMARC()) - ✅ Alignment checking (relaxed and strict modes)
- ✅ SPF alignment validation
- ✅ DKIM alignment validation
- ✅ Subdomain policies (
sp=tag)
- ✅ Policy lookup (
- Report Addresses:
- ✅
rua=(aggregate report recipients) - ✅
ruf=(forensic report recipients)
- ✅
- Policy Sampling:
- ✅
pct=percentage enforcement (line 50-60) - ✅ Random sampling to limit policy application
- ✅
- Dispositions: none, quarantine, reject
- Called from: smtp/inbound.go in message processing
- Status: Production ready
✅ YES - Fully implemented
- Location: auth/arc.go
- Components Implemented:
- ✅
ARCAuthenticationResults()- Builds authentication results header - ✅
ARCMessageSignature()- Signs message with ARC-Message-Signature header - ✅
ARCSeal()- Creates ARC-Seal header
- ✅
- Key Features:
- Instance tracking (i= tag)
- Algorithms: RSA-sha256, Ed25519-sha256
- Canonicalization: relaxed/relaxed
- Base64-encoded signatures
- Timestamp handling (t= tag)
- Applied in: delivery/worker.go via
addARCHeaders() - Status: Production ready
✅ YES - Fully implemented
- Location: auth/arc.go
- Functions:
- ✅
ValidateArcChainAtInstance()- Validates ARC chain (line 603) - ✅
verifyARCMessageSignature()- Verifies message signature (line 362) - ✅
verifyARCSeal()- Verifies seal signature (line 496)
- ✅
- Key Features:
- Chain validation up to specific instance
- Result status: pass/fail
- Cryptographic verification with RSA and Ed25519
- Called from: smtp/inbound.go message processing
- Status: Production ready
✅ YES - Fully implemented with SQLite
- Location: store/messages.go (line 497+) and store/schema.sql
- Table:
outbound_queue - Operations:
- ✅
EnqueueMessage()- Add to queue (line 505) - ✅
GetPendingQueue()- Retrieve ready entries (line 518) - ✅
UpdateQueueEntry()- Update status/retry info (line 547)
- ✅
- Status Fields: pending, sending, sent, failed
- DSN Tracking: DSN flags stored per entry (DSN_notify, DSN_ret, DSN_envid, DSN_sent)
- Status: Production ready
✅ YES - Exponential backoff implementation
- Location: delivery/retry.go
RetrySchedule.NextRetry() - Algorithm:
- Uses configured retry intervals (from config)
- After exhausting intervals, applies exponential backoff:
lastInterval × 2^(attempt - configLength) - Maximum retry time capped at 48 hours
- Respects SMTP 4xx temporary error codes
- Configuration:
delivery.retry_intervalsin config - Called from: delivery/worker.go when temporary failure detected
- Status: Production ready
✅ YES - Configurable worker pool
- Location: delivery/worker.go
Poolstruct - Features:
- ✅ Configurable number of workers (config:
delivery.queue_workers) - ✅ Mutex-protected claim mechanism to prevent duplicate processing
- ✅ Per-worker retry schedule
- ✅ Polling-based processing every 10 seconds
- ✅ Configurable number of workers (config:
- Pool:Start() launches workers (
delivery.queue_workerscount) - Status: Production ready
✅ YES - Automatic recovery implemented
- Location: store/messages.go
RecoverStaleQueueEntries()(line 560) - Logic: Finds entries stuck in "sending" status for > 30 minutes, resets to "pending"
- Called from: delivery/worker.go
Pool.Start()(line 39) on startup - SQL: Queries for entries where status='sending' AND age > 30 minutes
- Status: Production ready
✅ YES - DSN bounce notification system
- Location: delivery/worker.go
sendDSNReport()(line 462) - Functionality:
- ✅ Only sends DSN if NOTIFY parameter was set in MAIL FROM
- ✅ Filters for FAILURE notifications
- ✅ Constructs RFC 3464 multipart DSN message
- ✅ Extracts SMTP code from error message
- ✅ Records DSN sent status
- Message Disposition:
- Permanent failure (5xx) → Mark failed, send DSN immediately
- Temporary failure + max attempts reached → Send DSN
- Temporary failure with retries available → Schedule retry
- Related Code: reporting/dsn.go for DSN report generation
- Status: Production ready
✅ YES - Fully compliant implementation
- Location: mta_sts/fetcher.go
FetchPolicy() - Process:
- DNS TXT lookup:
_mta-sts.<domain>for policy ID - HTTPS fetch:
https://mta-sts.<domain>/.well-known/mta-sts.txt - Caching with expiration (respects
max_age) - Policy format parsing (mode, MX hosts, etc.)
- DNS TXT lookup:
- Policy Modes: enforce, testing, none
- Caching:
policyCachewith TTL based onmax_agefrom policy - Status: Production ready
✅ YES - Policy enforcement in outbound delivery
- Location: smtp/outbound.go
SendMail()(line 54-85) - Enforcement Logic:
- ✅ Fetches policy for recipient domain
- ✅ In
enforcemode: validates MX host in policy, fails if not listed, requires TLS - ✅ In
testingmode: logs violations but allows delivery - ✅ In
nonemode: no enforcement - ✅ Skips hosts not in policy during
enforcemode
- Failure Propagation: TLS failures recorded for TLS-RPT
- Status: Production ready
✅ YES - RequireTLS flag per domain
- Location: store/models.go
Domain.RequireTLSfield - Database Column:
domains.require_tls - Used in: delivery/worker.go
- Retrieves domain config
- Passes
requireTLStosmtp.SendMail()
- Effect: If true, delivery fails if TLS cannot be established
- Status: Production ready
❌ NO - Not implemented
- Location: tls/dane.go contains
GenerateTLSARecord()andGenerateTLSARecordFromDER()for record generation only - Missing: No TLSA record lookup, verification, or enforcement in smtp/outbound.go
- Current Status: Operators can generate TLSA records but verification is not performed
- Workaround: TLS verification relies on certificate chain validation only
- Note: This is a known limitation for enhanced security but basic opportunistic TLS still works
✅ YES - Fully implemented
- Location: dns/ptr.go
VerifyPTR() - Process:
- Reverse DNS lookup (
net.LookupAddr()) - Forward-confirm: resolve hostname back to same IP
- Returns: (hostname, isValid, error)
- Reverse DNS lookup (
- Called from: smtp/inbound.go in
handleConnection() - Caching: DNS results cached with TTL
- Logged: Results logged in smtp/inbound.go
- Usage: Stored in session, available in message processing context
- Status: Production ready
✅ YES - Per-domain configurable greylisting with smart cleanup
- Location: store/schema.sql (greylisting table), store/messages.go (
CheckGreylist(),CleanupGreylisting()), smtp/session.go (handleRCPT integration), web/handlers/admin.go, templates/admin_domain_edit.html - Features:
- ✅ Per-domain enable/disable toggle (default: OFF)
- ✅ Configurable delay per domain (5-480 minutes, default 15)
- ✅ Sender triplet tracking: (remote_IP, sender_email, recipient_email, recipient_domain)
- ✅ Three-state lifecycle: NEW (reject 450) → DELAYING (reject 421) → WHITELISTED (accept 250)
- ✅ Admin panel UI with checkbox and delay input
- Database:
- Table:
greylistingwith fields: id, recipient_domain, remote_ip, sender_email, recipient_email, first_seen, whitelisted_at, rejected_count - Indexes on domain, triplet, and first_seen for fast lookups
- UNIQUE constraint on (recipient_domain, remote_ip, sender_email, recipient_email)
- Table:
- SMTP Integration: Validated in
handleRCPT()after account verification- New triplets: Send 450 "Mailbox temporarily unavailable", record entry
- Delaying triplets: Send 421 "Service temporarily unavailable" until delay expires
- Whitelisted triplets: Continue with normal acceptance (250)
- Cleanup Strategy: Daily cleanup job removes rejected entries older than 30 days (where whitelisted_at IS NULL). Whitelisted senders remain in database permanently, preventing re-rejection after inactivity.
- Logging: Consistent
[smtp] greylisting:prefix for monitoring - Status: Production ready
✅ YES - Per-IP rate limiting on connections and messages
- Location: smtp/ratelimit.go
RateLimiterstruct - Limits:
- ✅ Connections per minute (configurable)
- ✅ Messages per minute (configurable)
- Implementation:
- Sliding window with 1-minute buckets
- Timestamp tracking per IP
- Automatic cleanup of stale entries every 5 minutes
- Functions:
AllowConnection()- Gate new connectionsAllowMessage()- Gate new messages (called in smtp/inbound.go for BDAT/DATA)
- Configuration:
smtp.rate_limit.connections_per_minute,smtp.rate_limit.messages_per_minute - Status: Production ready
✅ YES - Semaphore-based connection limit
- Location: smtp/inbound.go
connSemaphorechannel - Implementation:
- Semaphore with capacity
cfg.SMTP.MaxConnections - Each connection acquires slot on accept, releases on close
- Excess connections rejected
- Semaphore with capacity
- Configuration:
smtp.max_connectionsin config - Logging: Rate limit rejections logged (line 89)
- Status: Production ready
✅ YES - Per-domain configurable progressive delays with exponential backoff
- Location: store/schema.sql, store/messages.go, smtp/session.go, web/handlers/admin.go, templates/admin_tarpitting.html
- Features:
- ✅ Per-domain enable/disable toggle (default: OFF)
- ✅ Configurable max delay per domain (1-30 seconds, default 8)
- ✅ Remote IP tracking with failure count
- ✅ Exponential backoff delays: 0s, 1s, 2s, 4s, 8s, 16s, ..., up to configured max
- ✅ One-hour timeout resets failure counter
- ✅ Whitelist functionality to bypass delays for legitimate sources
- ✅ Admin panel UI with dashboard and per-domain control
- Database:
- Table:
tarpittingwith fields: id, recipient_domain, remote_ip, failure_count, last_invalid_command, first_failure, last_failure, whitelisted_at, notes - Indexes on domain, remote_ip, and first_failure for fast lookups
- UNIQUE constraint on (recipient_domain, remote_ip)
- Table:
- SMTP Integration: Applied on invalid SMTP commands
- Calculates delay using
calculateTarpittingDelay(failureCount, maxDelay) - Applies delay via
time.Sleep() - Increments failure counter on invalid command
- Calculates delay using
- Delay Calculation:
- 1st failure: 0s (free pass)
- 2nd failure: min(1s, maxDelay)
- 3rd failure: min(2s, maxDelay)
- 4th failure: min(4s, maxDelay)
- 5th+ failures: Continues exponential backoff capped at maxDelay
- Example: 30s max with 11 failures = min(512s, 30s) = 30s delay
- Admin Control: Checkbox to enable/disable + number input (1-30 seconds) for max delay configuration per domain
- Logging: Consistent
[smtp] tarpitting:prefix for monitoring - Status: Production ready
✅ YES - Multipart form with 25MB limit
- Location: web/handlers/compose.go
Send()method (line 68+) - Features:
- ✅
r.ParseMultipartForm(25 * 1024 * 1024)- 25MB limit - ✅ File attachment handling via form value
- ✅ Attachment save to disk via
parser.SaveAttachments() - ✅ Database records created in
attachmentstable
- ✅
- Storage Path:
cfg.Store.AttachmentsPath(typicallydata/attachments/) - Validation: Files stored and linked to message ID
- Status: Production ready
✅ YES - Secure file serving with MIME type handling
- Location: web/handlers/message.go
DownloadAttachment()(line 313) - Features:
- ✅ Attachment ID lookup in database
- ✅ Path traversal protection (uses filename from DB)
- ✅ Proper Content-Disposition header
- ✅ MIME type handling
- ✅ File existence verification
- Response Headers:
Content-Disposition: attachment; filename="..." - Status: Production ready
✅ YES - Plain text textarea for message body
- Location: templates/compose.html line 52
- Features:
- ✅
<textarea>element for plain text body - ✅ Optional pre-fill from reply context
- ✅ Form value:
name="body"
- ✅
- Status: Production ready
✅ YES - Both reply and forward implemented
- Reply: web/handlers/compose.go
- URL parameter:
?reply=true&to=...&subject= - Prefill logic in template
- URL parameter:
- Forward: web/handlers/forward.go
- Dedicated handler for forwarding messages
ForwardPage()for form display- Original message included in forwarded message
- Subject prefixed with "Fwd:"
- Status: Production ready
✅ YES - RFC-compliant XML report generation
- Location: reporting/dmarc.go
GenerateDMARCAggregateReport() - Features:
- ✅ XML structure per RFC 7489 Appendix A
- ✅ Report metadata (org, email, date range)
- ✅ Policy published section
- ✅ Per-record authentication results (SPF, DKIM)
- ✅ Policy evaluation (pass/fail/quarantine/reject)
- ✅ Source IP grouping
- Feedback Tracking: store/dmarc_feedback.go stores feedback records
- Status: Production ready
✅ YES - Weekly scheduled report distribution
- Location: reporting/scheduler.go
- Features:
- ✅ Weekly scheduler (Sunday 00:00 UTC)
- ✅ Configurable enable/disable:
config.dmarc.send_reports - ✅ Manual trigger:
SendReportsNow()function - ✅ Reports sent via email to
rua=addresses - ✅ GZIP compression before sending
- ✅ Base64 encoding
- Configuration:
dmarc.send_reportsboolean in config.json - Status: Production ready
✅ YES - RFC-compliant JSON report generation
- Location: reporting/tlsrpt.go
GenerateTLSReport() - Features:
- ✅ JSON structure per RFC 8460
- ✅ Date range specification
- ✅ Failure categorization by type
- ✅ MTA and IP tracking (if available)
- ✅ Aggregated failure counts
- Failure Types Tracked: TLS negotiation failures, certificate issues, etc.
- Data Source: store/tls_failures.go tracks TLS failures
- Status: Production ready
✅ YES - Weekly scheduled report distribution
- Location: reporting/scheduler.go
sendTLSRPTReportForDomain() - Features:
- ✅ Weekly scheduler (integrated with DMARC reports)
- ✅ DNS lookup for TLS-RPT addresses:
_smtp._tls.<domain>TXT record - ✅ Extraction of
rua=email addresses - ✅ Report delivery via queue
- Report Format: JSON per RFC 8460
- Manual Trigger: Via
SendReportsNow() - Status: Production ready
| Feature | Status | Notes |
|---|---|---|
| EHLO/HELO | ✅ YES | Production ready |
| 8BITMIME | ✅ YES | Production ready |
| SMTPUTF8 | ✅ YES | Production ready |
| CHUNKING/BDAT | ✅ YES | Production ready |
| PIPELINING | Advertised but not fully buffered | |
| DSN | ✅ YES | Production ready |
| SIZE | ✅ YES | Production ready |
| STARTTLS | ✅ YES | Production ready (inbound & outbound) |
| DKIM Signing | ✅ YES | Per-domain, RSA & Ed25519 |
| DKIM Verification | ✅ YES | DNS lookup & verification |
| SPF | ✅ YES | Full RFC 7208 compliance |
| DMARC | ✅ YES | Full RFC 7489 compliance |
| ARC Signing | ✅ YES | RFC 8617 compliant |
| ARC Verification | ✅ YES | RFC 8617 compliant |
| Queue Persistence | ✅ YES | SQLite based |
| Retry with Backoff | ✅ YES | Exponential backoff |
| Worker Pool | ✅ YES | Configurable workers |
| Stale Recovery | ✅ YES | Auto-recovery on startup |
| DSN Generation | ✅ YES | RFC 3464 compliant |
| MTA-STS Fetching | ✅ YES | RFC 8461 compliant |
| MTA-STS Enforcement | ✅ YES | enforce/testing/none modes |
| TLS per Domain | ✅ YES | RequireTLS flag |
| DANE Verification | ❌ NO | Records can be generated but not verified |
| PTR Verification | ✅ YES | Forward-confirmed |
| Rate Limiting | ✅ YES | Per-IP limits |
| Max Connections | ✅ YES | Semaphore based |
| Tarpitting | ✅ YES | Per-domain configurable with exponential backoff |
| Greylisting | ✅ YES | Per-domain configurable |
| Attachment Upload | ✅ YES | 25MB limit |
| Attachment Download | ✅ YES | Secure serving |
| Plain Text Compose | ✅ YES | Textarea input |
| Reply/Forward | ✅ YES | Both implemented |
| DMARC Reports | ✅ YES | Weekly distribution |
| TLS-RPT Reports | ✅ YES | Weekly distribution |
-
DANE Verification Not Implemented - TLSA records can be generated but not verified during outbound delivery. Current TLS relies on certificate chain validation only.
-
PIPELINING Partial - Advertised but command processing is sequential, not buffered. Clients should not exploit pipelining.
-
No Forward Secrecy Tracking - TLS sessions not analyzed for forward secrecy support.
-
Limited Policy Enforcement - MTA-STS policy validation could be enhanced with more granular error handling.
- Implement DANE Verification - Add TLSA record lookup and verification in outbound delivery for enhanced security
- Enhance PIPELINING - Implement command buffering to fully support pipelined SMTP
- Add TLS-ED Certificate Support - Track Ed25519 certificate usage in TLS-RPT
- Implement BIMI - Consider adding Brand Indicators for Message Identification support
All features have been verified in code. For production deployment:
- Use
config.dev.jsonfor testing (plaintext SMTP) - Test DKIM/SPF/DMARC with NIST DANE test validator
- Verify TLS certificates with Let's Encrypt ACME integration (configured in
tls/) - Monitor delivery queue with
go test ./deliveryfor retry logic