Ninety-Six Characters, Typed by Hand

October 16, 2026

There are ninety-six characters in this file that somebody typed in by hand.

Three random strings, thirty-two characters each. Then ninety-six more, for the second company.

resource "aws_route53_record" "workmail_dkim_limitless" {
  for_each = toset([
    "orf5c2cchlkzjys24rcu5tpk2ogt4an7",
    "op3dez7a2r4npmamjylo4ejcd2tiytbn",
    "q6ehf2rdshg2wz7yhx75ww5bhjq5yifa",
  ])

  zone_id = var.route53_zones.limitless["hostedzone"]
  name    = format("%s._domainkey", each.value)
  type    = "CNAME"
  ttl     = 3600
  records = [format("%s.dkim.amazonses.com.", each.value)]
}

resource "aws_route53_record" "workmail_dkim_glass" {
  for_each = toset([
    "mzmnwrgmrhfarsegondoqei4ksn6napf",
    "f7nlxuevsyieofaj4o6exszrbn6ie33p",
    "izt6ma6t2x5swvdwwqx4dijwss36ja4x",
  ])

  zone_id = var.route53_zones.glass["hostedzone"]
  name    = format("%s._domainkey", each.value)
  type    = "CNAME"
  ttl     = 3600
  records = [format("%s.dkim.amazonses.com.", each.value)]
}

They are signing keys. The mail service invents them, shows them on a web page, and expects to find them published in the domain's public records.

So someone opened that page, read them off, and retyped them into the source code. Twice.

Today they come out. Every one of them.

In their place, the code asks. It registers the domain, takes back whatever keys it is handed, and writes those into the zone. Three records, built in a loop, right by construction.

Nobody reads anything off a screen anymore.

And that is the small half of the day.

Because the file those keys came out of was two of everything. A block for one company, then the same block again, renamed, for the other. Two networks. Two directories. Two sets of records, side by side.

Every fix had to be made twice. And the second one was the one you forgot.

So it collapses into one shape and a list.

The definition stops naming companies at all. It becomes a table.

And each row carries a number, which becomes that company's private network.

Row zero gets one address block, row one the next. Add a third, pick the next number, and an entire isolated network appears with the mail machinery already inside it.

But the honest part of today is a comment at the top of the file.

Five numbered steps. Written for a person.

Because nothing can create the mail organization itself. That tool does not exist. So the machine builds the network, the directory, the records — and stops.

A human signs in, clicks through, and adds the users.

Then copies the organization's identifier, and pastes it back into the file.

Ninety-six characters of transcription, gone. One identifier still to go.

The Strings Are the Small Half

Welcome back.

I left you counting characters. Ninety-six of them, typed in by hand, and then ninety-six more for the second company. A hundred and ninety-two characters of transcription sitting in the source code of a business.

Today they all come out. But the strings are the small half of the story. The big half is why they were there at all.

Let's start with what those characters were.

When a mail service sends a message on your behalf, it signs it. Cryptographically. Every outgoing message carries a signature, and the receiving server on the other end checks that signature to decide whether the mail is really from you or from somebody wearing your name.

For that to work, the public half of the signing key has to be published somewhere the whole internet can look it up — which means in the naming records for your domain. Three keys, not one, because that's how you rotate a key without an outage: you publish the next one before you retire the last one.

So the mail service invents three keys per domain, displays them on a web page in its console, and then waits for them to show up in your public records.

There Is No Step That Checks the Typing

Now look at the chain of custody that creates.

The service generates the value. It shows the value on a screen. A person reads the value off the screen. That person types it — thirty-two characters of random lowercase letters and digits, with no words in it and no rhythm to it — into a file. Three times. Then does the whole thing again for the second company.

And there is no step in that chain that checks the typing.

Here's what makes that genuinely dangerous rather than merely tedious.

A wrong character doesn't break anything you can see. The record still exists. The record still looks like a record. The tool that manages your infrastructure is perfectly happy, because as far as it knows you asked for that string and it published that string.

What happens instead is that outgoing mail gets signed with a key whose public half nobody can find. Receiving servers check, fail to verify, and fall back to whatever standing instruction the domain has published about what to do with mail that doesn't check out.

And the standing instruction this business published, on day one, was: quarantine. All of it. No sampling.

So a single mistyped character means your own mail lands in other people's junk folders. Silently. No bounce, no warning. You find out when a client asks why you never replied to something you never received.

Right by Construction

So today, the transcription ends. And the way it ends is the interesting part.

The code stops carrying the answer and starts asking the question.

There's now a resource that says, in effect: turn on signing for this domain. And that resource, once it exists, hands back the three keys the service generated. They're not typed. They're not stored. They're read out of the service at the moment the change is applied.

resource "aws_ses_domain_dkim" "dkim" {
  for_each = var.ses_zones
  domain   = aws_ses_domain_identity.internal[each.key].domain
}

And then the records get built from a loop. For every domain in the list, for each of three keys, produce one alias record — named after the key, pointing at the provider's signing infrastructure.

resource "aws_route53_record" "dkim" {
  for_each = {
    for item in flatten([
      for key, zone in var.ses_zones : [
        for count in range(3) : {
          hostedzone   = zone.hostedzone
          record_count = count
          dkim_name    = "${aws_ses_domain_dkim.dkim[key].dkim_tokens[count]}._domainkey"
          dkim_record  = "${aws_ses_domain_dkim.dkim[key].dkim_tokens[count]}.dkim.amazonses.com"
        }
      ]
    ]) : "${item.hostedzone}-${item.record_count}" => item
  }

  zone_id = each.value.hostedzone
  name    = each.value.dkim_name
  type    = "CNAME"
  ttl     = 600
  records = [each.value.dkim_record]
}

Which means the number three appears exactly once in the file, as a count, instead of appearing as six separate hand-copied strings. And the value in the record cannot disagree with the value the service issued, because there is only one value and both ends read it from the same place.

That's the difference between a thing you verified and a thing that's right by construction. One of those needs to be re-checked every time it changes. The other one doesn't.

The Same Treatment Goes to the Ownership Record

The same treatment goes to the ownership record.

Before you're allowed to send as a domain at all, you have to prove you control it — the provider issues a token, you publish it, the provider looks it up. That token was also a literal string sitting in the file, forty-four characters of it, per company.

Now it's an attribute. The code registers the domain, takes back whatever token it's handed, and writes that into the zone.

resource "aws_ses_domain_identity" "internal" {
  for_each = var.ses_zones

  domain = each.value.domain
}

resource "aws_route53_record" "ses_ownership" {
  for_each = var.ses_zones

  zone_id = each.value.hostedzone
  name    = "_amazonses"
  type    = "TXT"
  ttl     = 3600
  records = [aws_ses_domain_identity.internal[each.key].verification_token]
}

Nobody reads anything off a screen anymore.

Two of Everything

And that — all of that — is the small half of the day.

Because the file those strings came out of had a much older problem. It was two of everything.

A block defining a private network for the first company. Then the same block again, renamed, for the second. Two pairs of subnets. Two generated administrator passwords, with identical rules typed out twice. Two corporate directories, each with its own hardcoded name, its own hardcoded alias, its own slightly differently worded description. Two permission documents. Two policies attaching them.

None of that is wrong, exactly. It works. It's readable. You can point at any block and say which company it belongs to.

The problem is what it does to every change that comes after.

Every Fix Has to Be Made Twice

Every fix has to be made twice. Every improvement has to be made twice. And the second one is the one you forget.

Worse — you don't find out that you forgot. The two blocks drift apart quietly, a line at a time, and six months later one company's directory has a setting the other one doesn't, and nobody remembers whether that was a decision or an oversight.

That's the real cost of copy-paste infrastructure. Not the duplication itself. The slow divergence, and the fact that reading the file no longer tells you whether the two things are the same.

It Becomes a Table

So today it collapses. And the collapse starts in the type declaration, which is the part I actually want to slow down on.

Before, the shape of the configuration said: this is an object with exactly two fields. One named for the consultancy, one named for the second brand. Each has a domain, a zone identifier, and a mail alias.

type = object({
  glass = object({
    domain     = string
    hostedzone = string
    alias      = string
  })
  limitless = object({
    domain     = string
    hostedzone = string
    alias      = string
  })
})

That declaration doesn't just describe the data. It enforces it. The number two is written into the schema. You cannot add a third company without editing the type.

After, it says: this is a map. Rows, each with the same columns. It doesn't say how many. It doesn't name any of them.

type = map(object({
  idx        = number
  domain     = string
  hostedzone = string
  alias      = string
  mail_orgid = string
}))

The definition stops knowing anything about who the customers are. It becomes a table.

The Module Stops Naming Companies

And with everything looping over that table, the module stops naming companies too.

The directory's full name is no longer typed out — it's built from the row's domain. Its short name is the row's own key. Its description is generic, because there's only one description now and it has to be true for everybody.

resource "aws_directory_service_directory" "activedirectory" {
  for_each = var.workmail_zones

  alias       = each.value.alias
  name        = "ad.${each.value.domain}"
  short_name  = each.key
  description = "ActiveDirectory for WorkMail"
  size        = "Small"
  enable_sso  = true
}

Search the mail module today and the words "limitless" and "glass" do not appear in it. Which is the actual test of whether a module is reusable. Not "could it be reused." Does it still know the name of the first thing it was built for?

One Thing Can't Be Looped

Except one thing can't be looped, and the way it's handled is the smartest small decision in here.

Every company's directory needs a private network of its own, and every network needs an address block that doesn't overlap with anybody else's. In the old file those were two literals: one company got the block ending in dot zero, the other the block ending in dot one.

You can't loop that. A loop would give every row the same address block, and two networks with the same addresses can never be joined.

So the table gains a column. A plain number, one per row. Row zero, row one. And the address block is assembled around it — same prefix, same size, that number in the middle.

resource "aws_vpc" "activedirectory" {
  for_each = var.workmail_zones

  cidr_block = "10.1.${each.value.idx}.0/24"

  tags = {
    Name = "${each.key}-activedirectory-vpc"
  }
}

Add a third company, pick the next number, and an entire isolated network appears with the mail machinery already inside it.

The Number Is Deliberately Dumb

Now, that number is assigned by a person. Which sounds like it just moved the hand-typing somewhere else, so let me defend it, because I think the choice is right.

The tool could generate that number automatically — count the rows, hand each one its position in the list. That works beautifully until somebody renames a row, or removes one, or the ordering changes for any reason at all. Then the numbers shift.

And a network whose address block changes is not an edited network. It's a destroyed network and a new one built in its place — taking the corporate directory inside it with it, and the mailboxes that directory backs.

So the number is deliberately dumb. It's a fact about the row, not a consequence of the row's position. Nothing you do to the rest of the table can move it.

One number, typed once, that pins a network in place forever. That's a very different thing from ninety-six characters of key material transcribed off a screen — even though both are, technically, somebody typing.

Which File Owns What

There's a second reorganization today, and it's about which file owns what.

Until now, all of the mail records lived in the mail module. The delivery address, the autodiscover record clients use to configure themselves, the sender policy, the signing keys, the quarantine rule — one pile.

Today that pile gets split down a real seam.

Receiving

Receiving stays in the mail module: the delivery record that says where messages physically go, and the record that lets somebody's phone find the settings on its own.

resource "aws_route53_record" "mx" {
  for_each = var.workmail_zones

  zone_id = each.value.hostedzone
  name    = ""
  type    = "MX"
  ttl     = 300
  records = ["10 inbound-smtp.us-east-1.amazonaws.com."]
}

resource "aws_route53_record" "autodiscover" {
  for_each = var.workmail_zones

  zone_id = each.value.hostedzone
  name    = "autodiscover"
  type    = "CNAME"
  ttl     = 3600
  records = ["autodiscover.mail.us-east-1.awsapps.com."]
}

Sending

Everything about sending moves to the sending module. Proof of ownership. The signing keys. The list of who's allowed to send as this domain. And the standing quarantine instruction.

Which is worth doing for one reason: those two groups fail in completely different ways. When receiving breaks, mail bounces and you know within minutes. When sending breaks, nothing bounces — the mail just quietly stops being trusted. Different failure, different blast radius, different file.

Failures Come Home

The sending side also picks up a piece that most setups never bother with, and I want to explain it because it's genuinely useful.

Every message has two return addresses. The one you see in your mail client, and an invisible one the servers use between themselves — the one delivery failures actually come back to.

By default, that second address belongs to the provider. Somebody bounces, and the report goes to a machine address at Amazon that has nothing to do with your company.

So today each domain gets a subdomain — literally named "bounce" — declared as the return path. And it's given the full treatment: its own sender policy record, and its own delivery record pointing at the provider's feedback endpoint.

resource "aws_ses_domain_mail_from" "bounce" {
  for_each = var.ses_zones

  domain           = each.value.domain
  mail_from_domain = "bounce.${each.value.domain}"
}

resource "aws_route53_record" "bounce_spf" {
  for_each = var.ses_zones

  zone_id = each.value.hostedzone
  name    = "bounce"
  type    = "TXT"
  ttl     = 3600
  records = ["v=spf1 include:amazonses.com ~all"]
}

resource "aws_route53_record" "bounce_mx" {
  for_each = var.ses_zones

  zone_id = each.value.hostedzone
  name    = "bounce"
  type    = "MX"
  ttl     = 300
  records = ["10 feedback-smtp.us-east-1.amazonses.com."]
}

The payoff is that failures come home. Your domain is the one that owns the bad news about your own mail, which is what you want the first time a large customer's mail server starts quietly refusing you.

This Exact Organization

One more piece of the sending side, and then we get to the honest part.

The mail service isn't allowed to send as your domain just because both live in your account. It needs an explicit grant — a policy attached to the domain saying: this specific service may send as me.

And that grant carries a condition, which is the part that matters. It doesn't say "the mail service." It says "the mail service, acting for this exact organization," identified by a long unique string.

condition {
  test     = "ArnEquals"
  variable = "aws:SourceArn"
  values = [
    "arn:aws:workmail:us-east-1:...:organization/${each.value.mail_orgid}"
  ]
}

Before, that string was buried inside two nearly identical permission documents, one per company. Today there's one document, built per row, with the organization identifier pulled from the table.

An Empty Filter Is a Sentence

And there's a small piece of wiring at the top of the project that tells you where this table is going.

The two modules don't get handed the table directly anymore. Each gets a filtered copy — walk the rows, keep the ones that belong here.

The mail module's filter excludes one row by name: the client automation that got switched on yesterday. That row is a domain this account owns, but it isn't a company with employees who need mailboxes.

The sending module's filter excludes nothing. It's written out anyway — the same construction, with an empty list of exclusions and a comment above it.

module "workmail" {
  source = "./modules/workmail"

  # exclude specific
  workmail_zones = { for k, v in var.route53_zones : k => v if !contains(["flywheel"], k) }
}

module "ses" {
  source = "./modules/ses"

  # exclude specific
  ses_zones = { for k, v in var.route53_zones : k => v if !contains([], k) }
}

Which is somebody saying: this is one register of every domain in this account now, not everything in it wants every service, and here is the line you'll edit when that changes. An empty filter is a sentence. It tells the next person the mechanism exists and they don't have to invent it.

Five Numbered Steps

And then there's the comment at the top of the mail module.

Five numbered steps. Written in plain language, for a human being.

### Due to a missing WorkMail terraform provider, the actual
### WorkMail Organization needs to be manually created after
### running this terraform.

### 1) Use existing Route53 domain,
### 2) Use WorkMail alias matching what's defined in variables.tf,
### 3) Use existing ActiveDirectory, targeting the ADs this creates
### 4) Organization -> Domains -> Set As Default
### 5) Create Users

Because the mail organization itself — the actual thing that holds the mailboxes — cannot be created by this tool. There's no support for it. It doesn't exist.

So the machine builds everything around the hole. The private network. The subnets in two separate buildings. The generated password. The corporate directory with single sign-on enabled. The delivery record, the autodiscover record, the ownership proof, the signing keys, the sender policy, the quarantine rule, the return path, the permission grant.

And then it stops.

A person signs in to a console. Points the new organization at the existing zone. Uses the alias that's already written in the variables file. Attaches the directory that was just created. Sets the domain as the default. Creates the users.

And then does one more thing. Copies the organization's identifier, and pastes it back into the table.

Which is, if you're keeping score, a person transcribing a string off a screen into the source code.

The exact thing this entire day was spent eliminating.

Put It in the Table

But it isn't the same, and the difference is worth naming.

The signing keys were transcription that didn't need to happen. The tool could always have asked for them. Somebody typed them because asking took an extra step, and the cost of that shortcut was a hundred and ninety-two characters that had to be right and couldn't be checked.

The organization identifier is different in kind. There is no interface. No amount of cleverness gets that value automatically, because the thing that produces it can only be produced by a human in a browser.

So the question isn't how to remove it. It's where to put it so it does the least damage.

And today's answer is: put it in the table. As a named column. Next to the domain, the zone, and the alias.

variable "route53_zones" {
  type = map(object({
    idx        = number
    domain     = string
    hostedzone = string
    alias      = string
    mail_orgid = string
  }))

  default = {
    glass = {
      idx        = 0
      domain     = "madeof.glass"
      hostedzone = "Z010939671A5NCHBBBCH"
      alias      = "madeofglass"
      mail_orgid = "m-ef980c95e8c94ba5a77ef5d302188ec5"
    }
    limitless = {
      idx        = 1
      domain     = "limitlessinteractive.com"
      hostedzone = "Z05575802ULDZNGHPUH6L"
      alias      = "limitlessllc"
      mail_orgid = "m-876cc9013dc548938a1201b52390dfde"
    }
  }
}

Not buried in a permission document that nobody opens. Not hidden two modules deep. In the one file a person reads when they want to know what this account contains — the register — with a five-step recipe at the top of the module explaining exactly how the value gets there.

That's the whole discipline of a documented manual step. You can't automate it. You can absolutely make sure that the next person finds it before it finds them.

So, the Day

So, the day.

A hundred and ninety-two characters of key material stopped being data and became a question the code asks. Two of everything became one shape and a list. A module that knew the names of its first two customers forgot them. Sending and receiving moved into separate files because they fail differently. Bounces started coming home. And the table that describes this business grew two columns — one that pins a network in place, and one that a human has to fill in.

The transcription didn't disappear.

It got a column, and a set of instructions, and a place where somebody will actually look for it.

valar morghulis