Lesson 10 / 16
Mail Delivery Issues
Modeling deliverability as a ratio: counting bounced messages, complaints, sudden volume spikes, and missing verification records separately, and measuring how many periods blacklisting and recovery take.
Published Updated
In this lesson
- Shows how reputation drops by counting the factors that degrade deliverability separately
- Derives the count of delayed and never-arriving messages, and the recovery time, in a one-period run
- Separates by name the part the sending side cannot measure, and states the remaining uncertainty
Contents
The previous three lessons established, at the record level, where the name points, who can send messages in that name’s name, and what happens to a noncompliant message. All three are measures of the sending side. Where this lesson starts, messages are now compliant, signed, and the policy is declared — and they may still not arrive.
The receiving side does not accept a message by looking only at the records. It also looks at the sender’s history: how many of its messages bounced in the recent period, how many people complained, whether volume spiked suddenly. The summary of this history is called reputation, and it behaves like a number: invisible while high, and it determines everything once it drops. The regional measurement network’s work-order notifications to field teams fall under this measure, and the network is fictional.
Deliverability Is a Ratio
When a message is sent, one of three outcomes happens: it reaches the inbox, it gets delayed and delivered later, or it never arrives. Deliverability is the ratio of these three outcomes — not a yes/no.
The factors that degrade the ratio can be counted separately, and all four pull in the same direction:
- Bounce rate. Sending to an address that no longer exists shows the list is not maintained.
- Complaint rate. A recipient marking a message as unwanted is the heaviest single factor.
- Sudden volume spike. Volume from the same address jumping to several times its usual level is a deviation from established behavior.
- Missing verification record. If the previous two lessons’ records are missing, it does not produce an incident by itself; it only lowers the highest reputation reachable.
The last item is the link connecting this topic: when sender verification is not set up, the resulting harm does not look like an incident — it behaves like a ceiling.
A One-Period Run
DR19. A period is one week and twenty periods are run; volume, rates, and thresholds are fictional. DR20. Reputation is modeled as a single number. DR21. Reputation falls fast and climbs slowly: it drops by at most 35 points in one period and rises by at most 5. DR22. Blacklisting happens below 35 points; exiting requires two periods above 55 points.
// Model: deliverability is a ratio, determined via reputation. Periods, // factors, and thresholds are fictional; not a real service's measurement. const STARTING_REPUTATION = 88; const BLACKLIST_ENTRY = 35; // the sending address is blacklisted below this value const BLACKLIST_EXIT = 55; // two periods are waited above this value // Period inputs: volume, bounce rate, complaint rate, whether the verification record is missing const PERIODS = [ { d: 1, volume: 400, bounceRate: 0.012, complaintRate: 0.0004, recordsMissing: true }, { d: 2, volume: 410, bounceRate: 0.011, complaintRate: 0.0005, recordsMissing: true }, { d: 3, volume: 395, bounceRate: 0.013, complaintRate: 0.0004, recordsMissing: true }, { d: 4, volume: 420, bounceRate: 0.012, complaintRate: 0.0006, recordsMissing: true }, { d: 5, volume: 405, bounceRate: 0.010, complaintRate: 0.0004, recordsMissing: true }, { d: 6, volume: 3200, bounceRate: 0.140, complaintRate: 0.0040, recordsMissing: true }, // legacy list added { d: 7, volume: 3100, bounceRate: 0.135, complaintRate: 0.0052, recordsMissing: true }, { d: 8, volume: 2900, bounceRate: 0.120, complaintRate: 0.0048, recordsMissing: true }, { d: 9, volume: 900, bounceRate: 0.040, complaintRate: 0.0020, recordsMissing: false }, // list cleaned up { d: 10, volume: 450, bounceRate: 0.015, complaintRate: 0.0008, recordsMissing: false }, { d: 11, volume: 430, bounceRate: 0.011, complaintRate: 0.0005, recordsMissing: false }, { d: 12, volume: 425, bounceRate: 0.009, complaintRate: 0.0004, recordsMissing: false }, { d: 13, volume: 415, bounceRate: 0.008, complaintRate: 0.0003, recordsMissing: false }, { d: 14, volume: 420, bounceRate: 0.008, complaintRate: 0.0003, recordsMissing: false }, { d: 15, volume: 430, bounceRate: 0.007, complaintRate: 0.0002, recordsMissing: false }, { d: 16, volume: 425, bounceRate: 0.007, complaintRate: 0.0002, recordsMissing: false }, { d: 17, volume: 440, bounceRate: 0.006, complaintRate: 0.0002, recordsMissing: false }, { d: 18, volume: 435, bounceRate: 0.006, complaintRate: 0.0002, recordsMissing: false }, { d: 19, volume: 430, bounceRate: 0.005, complaintRate: 0.0001, recordsMissing: false }, { d: 20, volume: 445, bounceRate: 0.005, complaintRate: 0.0001, recordsMissing: false }, ]; // Bands: by reputation range, [inbox, delayed, undelivered] const BANDS = [ [80, [0.98, 0.02, 0.00]], [60, [0.88, 0.10, 0.02]], [40, [0.65, 0.25, 0.10]], [20, [0.35, 0.35, 0.30]], [0, [0.10, 0.30, 0.60]], ]; const BLACKLIST_BAND = [0.05, 0.15, 0.80]; const band = (reputation) => BANDS.find(([threshold]) => reputation >= threshold)[1]; const clamp = (x) => Math.max(0, Math.min(100, x)); const MAX_DROP = 35; // the most points it can fall in one period const MAX_RECOVERY = 5; // the most points it can climb in one period let reputation = STARTING_REPUTATION; let blacklisted = false; let cleanPeriods = 0; let previous = PERIODS[0].volume; const history = []; for (const g of PERIODS) { const growth = g.volume / previous; let target = 100; target -= Math.max(0, g.bounceRate - 0.02) * 300; // bounce rate target -= Math.max(0, g.complaintRate - 0.001) * 5000; // complaint rate target -= growth > 3 ? 20 : 0; // sudden volume spike target -= g.recordsMissing ? 12 : 0; // missing verification record target = clamp(target); reputation = target > reputation ? Math.min(target, reputation + MAX_RECOVERY) : Math.max(target, reputation - MAX_DROP); if (!blacklisted && reputation < BLACKLIST_ENTRY) { blacklisted = true; cleanPeriods = 0; } else if (blacklisted) { if (reputation >= BLACKLIST_EXIT) cleanPeriods++; else cleanPeriods = 0; if (cleanPeriods >= 2) blacklisted = false; } const [ik, dk, uk] = blacklisted ? BLACKLIST_BAND : band(reputation); history.push({ ...g, target: Math.round(target), reputation: Math.round(reputation), blacklisted, inbox: Math.round(g.volume * ik), delayed: Math.round(g.volume * dk), undelivered: Math.round(g.volume * uk), }); previous = g.volume; } console.log('period volume bounce% complaint% reputation black inbox delayed undelivered'); for (const s of history) { console.log( `d${String(s.d).padStart(2, '0')}`.padEnd(8) + String(s.volume).padEnd(8) + (s.bounceRate * 100).toFixed(1).padEnd(9) + (s.complaintRate * 100).toFixed(2).padEnd(12) + String(s.reputation).padEnd(12) + (s.blacklisted ? 'yes' : '-').padEnd(7) + String(s.inbox).padEnd(7) + String(s.delayed).padEnd(9) + String(s.undelivered), ); } const total = (field) => history.reduce((t, s) => t + s[field], 0); console.log('\ntotal volume:', total('volume'), '| inbox:', total('inbox'), '| delayed:', total('delayed'), '| undelivered:', total('undelivered')); console.log('delivery rate:', (total('inbox') / total('volume') * 100).toFixed(1) + '%', '| loss rate:', (total('undelivered') / total('volume') * 100).toFixed(1) + '%'); const after = history.filter((s) => s.d >= 10); const sum = (arr, field) => arr.reduce((t, s) => t + s[field], 0); console.log('after the incident (d10-d20) volume:', sum(after, 'volume'), '| delayed:', sum(after, 'delayed'), '| undelivered:', sum(after, 'undelivered')); const declineStart = history.find((s) => s.reputation < 80); const bottom = history.reduce((a, b) => (b.reputation < a.reputation ? b : a)); const recoveryPoint = history.find((s) => s.d > bottom.d && s.reputation >= 80); console.log('\nreputation started falling: d' + declineStart.d, '| bottom:', bottom.reputation, '(d' + bottom.d + ')', '| periods spent blacklisted:', history.filter((s) => s.blacklisted).length); console.log('decline: 1 period | return to 80 threshold: d' + recoveryPoint.d, '| recovery:', recoveryPoint.d - bottom.d, 'periods');
period volume bounce% complaint% reputation black inbox delayed undelivered d01 400 1.2 0.04 88 - 392 8 0 d02 410 1.1 0.05 88 - 402 8 0 d03 395 1.3 0.04 88 - 387 8 0 d04 420 1.2 0.06 88 - 412 8 0 d05 405 1.0 0.04 88 - 397 8 0 d06 3200 14.0 0.40 53 - 2080 800 320 d07 3100 13.5 0.52 33 yes 155 465 2480 d08 2900 12.0 0.48 38 yes 145 435 2320 d09 900 4.0 0.20 43 yes 45 135 720 d10 450 1.5 0.08 48 yes 23 68 360 d11 430 1.1 0.05 53 yes 22 65 344 d12 425 0.9 0.04 58 yes 21 64 340 d13 415 0.8 0.03 63 - 365 42 8 d14 420 0.8 0.03 68 - 370 42 8 d15 430 0.7 0.02 73 - 378 43 9 d16 425 0.7 0.02 78 - 374 43 9 d17 440 0.6 0.02 83 - 431 9 0 d18 435 0.6 0.02 88 - 426 9 0 d19 430 0.5 0.01 93 - 421 9 0 d20 445 0.5 0.01 98 - 436 9 0 total volume: 16875 | inbox: 7682 | delayed: 2278 | undelivered: 6918 delivery rate: 45.5% | loss rate: 41.0% after the incident (d10-d20) volume: 4745 | delayed: 403 | undelivered: 1078 reputation started falling: d6 | bottom: 33 (d7) | periods spent blacklisted: 6 decline: 1 period | return to 80 threshold: d17 | recovery: 10 periods
Asymmetry
One line carries this entire lesson’s weight: decline takes one period, recovery takes ten.
The incident starts at d06. A set of addresses left over from years earlier is added to the
field list; volume jumps to eight times its size, the bounce rate spikes to 14%, and
complaints increase. Three factors hit in the same period, and reputation drops from 88 to
53. One period later it falls to 33 and gets blacklisted.
What follows from here is no longer about sending behavior. At d09 the list is cleaned up,
at d10 volume returns to normal and the rates are good — but reputation can only climb five
points per period. The address stays blacklisted for six periods; after coming off the list,
returning to the 80 threshold takes four more periods. Fixing the mistake took one period;
erasing its consequence took ten. This asymmetry is deliberate and serves the receiving side:
a measure that falls fast stops abuse early, and a measure that climbs slowly requires good
behavior to be sustained for several periods.
The second number shows the price of this. In the eleven periods after the incident — that is, while sending was entirely normal — 1,078 messages never arrive and 403 messages get delayed. These are work-order notifications going to the field team, and there is nothing wrong with their content; they get dropped purely because of the sender’s history. The 41.0% loss rate in the period total is misleading — most of that loss was already messages that should not have been sent in the first place. The real cost of this is the normal messages lost after the incident.
Counting the Factors Separately
Four factors hit the same number, but their contributions are not equal, and the weights in the model show this.
Bounce rate is the largest single item: at 14%, it alone takes away 36 points. Complaint rate carries similar weight with a number that looks smaller — five complaints per thousand means 21 points; this is the factor where a small number of receivers’ decisions are weighted very heavily. A sudden volume spike is a fixed 20-point penalty, independent of content: had the same messages been sent more slowly, this item would never have arisen.
The fourth behaves differently from the others. The missing verification record produces no
incident at all in the first five periods; the delivery ratio is 98%, nothing is visible. All
it does is hold the reputation ceiling at 88. What this means becomes clear at d19 and
d20: once the record is completed, reputation climbs to 93, then to 98. In other words,
while the record was missing, the system always sat 12 points lower, and when an incident
arrived, it was 12 points closer to the blacklist threshold. Here, the previous two lessons’
records are measured not as a defense but as a buffer.
Separating Flows, and the Surface It Brings
All the loss in this run comes from a single number. Reputation is kept per sender, and the model has exactly one sender: both the bulk announcement and the work-order notification go out under the same name. The reputation the bulk list damages also drags down the work order going to the field team — even though the two flows have no connection to each other.
The countermeasure is separating the flows: bulk and opt-in sending is done from a separate subdomain, and operational notifications stay under their own name. The separation splits reputation in two as well; what happens on one side does not drag down the other. The model’s structure says this directly, because the bands derive from reputation, and reputation is tied to a single sender.
The new surface separation brings is the sum of the first three lessons. Every new subdomain wants its own set of records: an address record, a sender policy record, a signature record, its own policy declaration, and its own report address. The number of records in the zone grows, the chance of a dangling record grows, and the time-to-live decision has to be made once more. The inventory drift counted in the first lesson, the missing records counted in the second, and the unprepared flow counted in the third — all three are born from scratch for the new name. The step that reduces the delivery problem doubles the maintenance surface, and that cost is part of the decision to separate the name.
The Part That Cannot Be Measured
DR23. The bands convert the reputation range directly into a delivery ratio. DR24. In the model, the sending side sees the count of messages that reach the inbox.
The last assumption does not hold in reality, and it is the part of this topic that cannot be measured. The sending side knows only two things: whether the message was accepted by the receiving side, and which messages bounced. Whether an accepted message lands in the inbox or somewhere else is invisible. The “inbox” and “delayed” columns counted separately in the table above are, in production, a single column: accepted.
Reputation itself is not published either. It can be modeled as a number, but it cannot be read; every receiving side keeps its own measure, and these measures differ from one another. Blacklist status can be partly queried, but which threshold the listing decision used, and how long the exit will take, is unknown from the outside.
Because of this, the only thing that can be measured is indirect indicators: bounce rate, complaint reports, sending to one’s own control addresses, and the previous lesson’s report flow. All four show not the outcome itself but the outcome’s shadow. When a delivery problem started is most often learned when a recipient says “the notification is not arriving”; that is a tip-off, not a measurement.
Summary
- Deliverability is not a yes/no; it is the ratio of three outcomes: inbox, delay, never arriving.
- Four factors can be counted separately; in the model, bounce rate takes away 36 points, complaints 21, sudden volume spike 20, and the missing verification record sets a 12-point ceiling.
- Decline takes one period, recovery takes ten: reputation drops from 88 to 53 in one period, six periods pass on the blacklist, and returning to the 80 threshold takes ten periods after the bottom.
- Even after the incident, while sending is entirely normal, 1,078 messages never arrive and 403 get delayed; these are work-order notifications with nothing wrong in their content.
- Verification records do not prevent an incident, they raise the ceiling; while missing, the system sits 12 points closer to the blacklist threshold at all times.
- The part that cannot be measured is the receiver’s decision: the sending side sees acceptance and rejection, not whether a message lands in the inbox, and reputation is published nowhere.
Next Step
This topic has closed the name. The name now knows where it points, who may send messages in its name is on record, what happens to a noncompliant message is declared, and where delivery breaks down can be measured. Every item closed across the four lessons was about the name.
What remains is what the name points to. Every address record in the zone file leads to a machine, and that machine has been sitting in a state never questioned throughout this topic: reachable from everywhere since the day it was set up. Which ports are listening, where administrative access is open from, and how many credentials with no expiry sit on it were never counted. The next topic shifts the scale from the name to the machine and asks the same question there: how many items does the default setup leave open?
To keep your progress and take notes, Log in
My notes
Log in to take notes.