Skip to main content

Email Sending — Technical Documentation

Last updated: February 2026


Table of Contents

  1. Overview
  2. Architecture
  3. Repository Map
  4. Core Flows
  5. Entities & Data Model
  6. Queue Processing
  7. Services & Business Logic
  8. Postal Integration
  9. Tracking System
  10. Response Handling
  11. Configuration
  12. Integration Points

1. Overview

Email Sending handles campaign email delivery via Sopro's self-hosted Postal mail server. The system manages scheduling, queue-based sending, click/open tracking via rewritten URLs and tracking pixels, and response handling (bounces, replies, AI classification). Primary implementation is in sopro-sodastream (scheduling, sending, tracking) and sopro-sodastream-core (queue consumers, shared utilities).


2. Architecture


3. Repository Map

RepositoryLayerKey Files
sopro-sodastreamSenderPostalAPISender.cs
sopro-sodastreamSchedulingCampaignEmailSchedule/
sopro-sodastreamTracking ControllerControllers/TrackingController.cs
sopro-sodastreamResponse HandlerControllers/ResponseController.cs
sopro-sodastreamURL RewritingHelpers/UrlRewriter.cs
sopro-sodastreamTracking PixelHelpers/TrackingPixelHelper.cs
sopro-sodastreamEntitiesModels/CampaignEmailSchedule.cs, EmailMessage.cs
sopro-sodastream-coreQueue ConsumerSoProQueueConsumer/PostalMessageHandler.cs
sopro-sodastream-coreShared UtilsUtils/EmailHelpers/
sopro-sodastream-coreQueue TypesSoProQueueDataApi/

4. Core Flows

4.1 Email Scheduling & Sending

4.2 Click Tracking Flow

4.3 Open Tracking Flow

4.4 Response Handling Flow


5. Entities & Data Model

CampaignEmailSchedule

ColumnTypeDescription
Idint (PK)Schedule entry identifier
CampaignIdint (FK)Campaign reference
ProspectIdlong (FK)Prospect to email
ScheduledAtdatetimeWhen the email should be sent
Statusint (enum)Pending, Sent, Failed, Cancelled
EmailTypeint (enum)Initial, Chaser1, Chaser2, Chaser3
SentAtdatetime?Actual send timestamp
PostalMessageIdnvarchar(255)Postal message ID for tracking

EmailMessage

ColumnTypeDescription
Idint (PK)Message identifier
CampaignIdint (FK)Campaign reference
ProspectIdlong (FK)Recipient prospect
Subjectnvarchar(500)Email subject line
Bodynvarchar(max)Email HTML body
FromEmailnvarchar(255)Sender email address
FromNamenvarchar(255)Sender display name
PostalMessageIdnvarchar(255)Postal tracking ID
SentAtdatetimeSend timestamp
Statusint (enum)Sent, Delivered, Opened, Clicked, Replied, Bounced

EmailTrackingEvent

ColumnTypeDescription
Idint (PK)Event identifier
MessageIdint (FK)Related email message
EventTypeint (enum)Open, Click, Bounce, Reply, Complaint
OccurredAtdatetimeEvent timestamp
IPAddressnvarchar(45)Source IP
UserAgentnvarchar(500)Client user agent string
ClickUrlnvarchar(2000)Clicked URL (for click events)

6. Queue Processing

Queue Configuration

SettingValue
Queue TypeAzure Queue (Type 46)
ConsumerSoProQueueConsumer in sopro-sodastream-core
Message FormatJSON with campaign ID, prospect ID, message content

SoProQueueTypeEnum

The email sending queue type is defined in the shared queue type enum:

PostalMessage = 46 // Email delivery via Postal

Processing Pipeline


7. Services & Business Logic

PostalAPISender

Location: sopro-sodastream/PostalAPISender.cs
Purpose: Sends emails through the self-hosted Postal server via HTTP API.

MethodDescription
SendAsync()Sends a single email message via Postal
BatchSendAsync()Sends multiple messages in batch

CampaignEmailSchedule (Service)

Location: sopro-sodastream/CampaignEmailSchedule/
Purpose: Calculates send slots and enqueues messages.

MethodDescription
CalculateSendSlots()Distributes daily volume across send window
EnqueueScheduledEmails()Places ready-to-send messages in Azure Queue

Queue Consumer Handler

Location: sopro-sodastream-core/SoProQueueConsumer/PostalMessageHandler.cs
Purpose: Processes queued messages — personalises, adds tracking, sends.


8. Postal Integration

Server Details

SettingValue
ServerSelf-hosted Postal
Address193.9.15.161:5000
ProtocolHTTP API (REST)
AuthAPI key per mail server organisation

API Usage

// Sending via Postal API
POST http://193.9.15.161:5000/api/v1/send/message
Headers: X-Server-API-Key: {api-key}
Body: {
"to": ["prospect@company.com"],
"from": "sender@client-outreach.com",
"subject": "...",
"html_body": "...",
"tag": "campaign-{campaignId}"
}

Postal Webhooks

Postal sends webhook notifications for delivery events:

EventDescription
MessageSentEmail successfully delivered to recipient server
MessageBouncedDelivery failed (hard or soft bounce)
MessageHeldMessage held for review

9. Tracking System

URL Rewriting

All links in outgoing emails are rewritten to pass through the tracking controller:

Original: https://www.client.com/demo
Rewritten: https://track.client-outreach.com/c/{trackId}

The trackId encodes the message ID, prospect ID, and original URL.

Tracking Pixel

A 1x1 transparent PNG is appended to the email HTML body:

<img src="https://track.client-outreach.com/o/{trackId}.png" width="1" height="1" style="display:none;" />

Tracking Domain

Each campaign sender has a dedicated tracking domain (e.g., track.client-outreach.com) that points to Sopro's tracking infrastructure. This:

  • Avoids using Sopro's primary domain (reputation isolation)
  • Matches the sender's branding
  • Passes email security checks (SPF, DKIM)

10. Response Handling

Reply Processing

Replies are received and processed through:

  1. Postal webhook notifies of incoming reply
  2. ResponseController receives and parses the reply
  3. AI Classification categorizes the reply (positive, negative, referral, OOO, query)
  4. Forwarding — reply is forwarded to the client via Sodastream
  5. Statistics — campaign stats updated

Bounce Handling

Bounce TypeTreatment
Hard bounce (550, 551, 552)Prospect marked as undeliverable, suppressed
Soft bounce (450, 451, 452)Retry up to 3 times, then treat as hard bounce
Spam complaintImmediate suppression from all campaigns

11. Configuration

Postal Server Config

Configured per environment in Sodastream's web.config / app settings:

PostalApiUrl=http://193.9.15.161:5000/api/v1
PostalApiKey=<per-organisation-key>

Queue Configuration

Azure Queue connection string in application settings:

{
"AzureStorage": {
"ConnectionString": "<azure-storage-connection>"
}
}

Tracking Domain Setup

Each client tracking domain requires:

  • DNS A record → Sopro tracking server
  • SSL certificate (auto-provisioned or manual)
  • Registration in Sodastream campaign settings

12. Integration Points

Upstream (data flows in)

SourceDataMechanism
Campaign ManagementCampaign config, audience, scheduleDatabase
Generative MessagingAI-generated email contentDatabase (stored by AIM)
Email FindingProspect email addressesDatabase

Downstream (data flows out)

TargetDataMechanism
Postal ServerEmail messages for deliveryHTTP API
Campaign StatisticsOpens, clicks, replies, bouncesDatabase + webhooks
Sodastream CRMReply forwarding to clientsInternal processing
Prospect RecordsEngagement status updatesDatabase

Cross-Feature Dependencies

FeatureRelationship
Campaign ManagementCampaign config drives scheduling and sending
Generative MessagingAIM generates the email content that gets sent
Email FindingFound emails are the delivery addresses
Verification PipelineVerified emails reduce bounce rates
Web Intent TrackingWebsite visits may trigger campaign engagement