Skip to content

Self-Update

A ServerlessInbox deployment is not a snapshot of whatever was current on the day you launched it. It watches for published releases and applies them on its own. This is worth knowing up front, because discovering it later — after a version you did not install starts running — is a bad way to learn it.

The behaviour is controlled by a single CloudFormation parameter, artifactTracking, which names the level your deployment is pinned at. It follows the newest release inside that track:

  • pinned — nothing updates itself. The operator decides when a new version is deployed.
  • minor — track v{major}.{minor}. The deployment follows patch releases only inside its current minor version. This is the default.
  • major — track v{major}. The deployment follows new minor and patch releases within its current major version, picking up new features without breaking changes. It never crosses a major-version boundary.

Because the default is minor, a deployment you launch today and never touch again will keep moving forward. That is the intended behaviour for most installations: a mail server that nobody updates is a mail server accumulating unpatched bugs, and the people running these deployments are generally not running a release-tracking process for them.

The artifactTracking parameter is only meaningful because releases follow semantic versioning strictly. In semver, a patch release contains only bug fixes and does not change any public interface; a minor release adds new features and is backwards-compatible with every earlier release in the same major version; a major release is where breaking changes are permitted.

Releases of ServerlessInbox are currently pre-1.0.0. Semver places anything below 1.0.0 outside its compatibility guarantee entirely: a 0.x minor release is permitted to break things. The product does not intend to use that permission. From now on the aim is to make no breaking changes at all during the beta, holding the same discipline that applies after 1.0.0. That is an intention rather than a guarantee, and it is the standard the releases are held to.

The practical consequence is that the default minor setting is the conservative choice: it keeps your deployment moving forward for fixes and compatible features, within the version line you chose, and the tracking discipline means that forward motion is predictable. The pinned setting is there for operators who want to decide every version change themselves, holding the risk of unpatched bugs but keeping full control.

An updater component inside the deployment resolves the currently published artifact versions and applies them. It can update three things, independently of each other:

  • the Lambda code,
  • the CloudFront edge functions,
  • the UI assets.

Independently matters. A UI change does not force the mail-processing Lambdas to be replaced, and a Lambda update does not have to wait for anything else to be ready.

Self-updating infrastructure is a reasonable thing to be nervous about. Three properties are what make it defensible here.

The new Lambda is smoke-tested before it is accepted

Section titled “The new Lambda is smoke-tested before it is accepted”

Before an updated Lambda is left in place, the updater invokes it with a probe payload. The function recognises that payload and answers it without doing any real work — no mail is processed, nothing is written, no message moves. The probe is a question about readiness, not a transaction.

What the answer asserts varies per function, but it is consistently about startup health rather than behaviour: that the function’s configuration and environment load and validate, and that its critical dependencies actually answer. The checks range from verifying that an IAM role is present to confirming a CloudWatch log group is readable to performing a read against the DynamoDB table a function depends on — all quick verifications that catch the common sources of update failures.

If the probe fails, the updater restores the function to the version that was previously deployed. The rollback is per-function: one bad artifact does not take the deployment with it, and it does not leave the rest of the system stranded on a half-applied release.

A probe result that cannot be classified is treated as inconclusive, and the update proceeds. This is a deliberate choice in favour of not blocking updates on ambiguity, and it is the reason the probe is designed to be cheap and unambiguous when it does answer.

The updater is part of the open infrastructure code. You can read exactly how version resolution, probing and rollback work, rather than taking this page’s word for it. See Open Source for what is open and where it lives.

A smoke test proves that a function starts up correctly with its dependencies reachable. That is a real and useful guarantee — the large majority of update failures are configuration drift, a missing permission, or a dependency that moved — but it is not an end-to-end test of mail flow.

Nothing in the update path sends a message through the full pipeline and checks it came out the other side. A release that starts cleanly and validates its configuration, but handles some message shape incorrectly, will pass the probe. If you need that level of assurance before a version reaches your production mailboxes, the probe is not the mechanism that gives it to you.

pinned exists for operators who want the decision themselves. The clearest case is a beta: if you are running a pre-release channel, the thing you are testing is precisely the code that has not been proven yet, and having it replaced underneath you mid-test destroys the test. Pinning holds the deployment still so that what you observed yesterday is still what is running today.

The same reasoning applies to any deployment operating under a change-control regime, or to one where you want a version change to line up with a maintenance window rather than arriving on its own schedule. The trade is explicit: pinning means nothing moves without you, including fixes.