Skip to content

Part 7 of 7 · Visitor check-in logger series ~7 min read

Engineering reference: the visitor check-in logger architecture

The first six posts are for the person deciding whether to build this. This one is for the person building it. Same system, no analogies: the services by name, the functions, the two tables, the offline roll call, and the retention rule.

Key takeaways

  • Single region, single account. Every resource is regional; nothing is global except the IAM roles.
  • 3 Lambda functions, each with its own execution role. No shared role, no wildcards on resources.
  • 2 DynamoDB tables, each keyed so the concurrency story is a condition expression rather than a lock.
  • One Bedrock model, called once, with a JSON schema it must fill or leave null.
  • Nothing always-on: no instance, no container, no provisioned capacity.

The system, by service name

The visitor check-in logger drawn with AWS service namesThree boxes across the top outside the AWS account. The check-in screen and a door screen. Hosts, who are notified and asked. And Roll call devices, cached and offline capable. Inside the account, three groups. An API for check in and out alongside EventBridge running the sweep and prompts. Three Lambda functions named checkin, checkout and sweep. And two DynamoDB tables named visits and people. A note gives the region as us-east-1, one account, and states that visit records expire at thirty days by TTL and no sweep runs during the day.AWS ACCOUNTThe check-in screenand a door screenHostsnotified, and askedRoll call devicescached, offline capableAPI + EventBridgecheck in and out,sweep and promptsLambda x3checkin, checkout, sweepDynamoDB x2visits, peopleingroundsoutus-east-1. One account. Visit records expire at 30 days by TTL; no sweep runs during the day.
Fig 1. The same shape as Part 1 with the service names filled in. Nothing here is new; it is the same three groups, named.
  • Compute
  • Database
  • App integration
  • Networking
  • Security & identity
  • Front-end & mobile

Region and account

  • Region: us-east-1. Chosen because SES inbound receipt rules exist in only a subset of regions and this one has the widest Bedrock model availability. If your data has to stay elsewhere, check both constraints before moving: inbound SES is the binding one.
  • Account: one. This is a small system, and a separate account per environment costs more in wiring than it saves. A dev and a prod stack in the same account, with distinct resource prefixes, is the right size here.
  • Everything is regional. The only global resources are the IAM roles and policies. There is no CloudFront, no global table and no cross-region replication, because nothing here has a latency or durability requirement that would justify them.

Lambda inventory

FunctionTriggerDoesTimeout / memory
vl-checkinAPI, from the screenCreates the visit, notifies the host, checks induction currency for known contractors10s / 512 MB
vl-checkoutAPI, from any of the four mechanismsCloses the visit as confirmed, recording which mechanism did it10s / 512 MB
vl-sweepEventBridge, at the site’s stated end of daySends a final host prompt, then closes the rest as assumed; never runs mid-day60s / 512 MB

Splitting this into separate functions is not about modularity. It is that only one of them needs Bedrock permissions and only one is reachable from the public internet, and neither of those is true if it is one handler behind a router.

IAM, scoped

RoleAllowedOn
vl-checkin-roledynamodb:PutItem, dynamodb:GetItem, ses:SendEmailVisits; read-only on people; one verified identity
vl-checkout-roledynamodb:UpdateItemVisits only
vl-sweep-roledynamodb:Query, dynamodb:UpdateItem, ses:SendEmailVisits; one verified identity

No role has a Resource: “*” on anything that writes, and every GetSecretValue grant names a single secret arn. That is why there is more than one secret rather than one JSON blob with everything in it.

DynamoDB schemas

Table: visits

PK   site_id           S
SK   checked_in_at#id  S   sorts the roll call by arrival naturally
     name              S
     host_id           S   the roll call groups on this
     kind              S   visitor | contractor | delivery
     checked_out_at    S
     checkout_by       S   visitor | host | badge | sweep
     confirmed         BOOL false when closed by the sweep
     mobile            S   only if the site uses it, and says what for
     ttl               N   epoch, +30 days

`checkout_by` is what makes the confirmed-versus-assumed chart in
Part 4 possible, and that chart is the only measure of accuracy.

Table: people

PK   person_key        S   a code on a contractor card
     display_name      S
     kind              S   contractor | regular_visitor
     inducted_at       S
     induction_expires S   checked at every check-in
     inducted_by       S   a person, recorded
     rules_version     S   which site rules the induction covered

Deliberately holds no certificate scans. Recording that competencies
were checked, by whom and when, is the assurance; the documents are
somebody else’s personal data.

Inbound and outbound

  • Check-in is name and host and nothing else unless the site has stated a reason for another field.
  • The roll call is pushed to devices and cached, refreshed while a network exists and readable when it does not. It requires no login at the point of use.
  • Four check-out paths write the same record with different checkout_by values, so coverage per mechanism is measurable.
  • The sweep time is per site, with a separate out-of-hours configuration, because a nine-to-five sweep would close a Sunday shift.

The model call

  • There is no model in this system. It is a list with timestamps.
  • The tempting use is predicting when a visitor has probably left. That is exactly what the assumed sweep does, and dressing it up as a prediction would let it run mid-day.
  • The wrong use is facial recognition. It converts a visitor log into a biometric system with a different set of obligations and no additional safety benefit.
  • A defensible use is matching a typed name against known contractors, so a regular does not create a new person record every visit.
  • The cost page assumes none, which is why messaging is the whole variable.

Things worth knowing before you build it

  • Never run the sweep during the working day. Auto-closing somebody at two o’clock removes them from the roll call at exactly the hour the alarm might go.
  • Record which mechanism closed each visit. Without it, confirmed and assumed are indistinguishable and the roll call’s accuracy cannot be measured or improved.
  • Group the roll call by host, not alphabetically. It matches how confirmation actually happens at an assembly point.
  • Cache the roll call on the devices. A web page is not available in a car park with the power off.
  • Set a TTL on visit records from the first day. A multi-year log of who met whom is a liability nobody chose to create.

That is the whole system. Seven posts, one diagram at a time, and nothing in it that needs a server.

All posts