Picture-Day Mailboxes · photos.email

The picture-day mailbox a school photo program runs on

Picture-Day Mailboxes carries the messages a school photo program depends on -- the picture-day reminder, the gallery-is-ready notice, the order receipt. They go out under the school's brand, families reach their own child's photos through a permission-checked link rather than a face match, and contact details stay inside our own private system instead of an outside marketing company. The address is open to claim.

Example address: [email protected]. Checkout is not open yet — the figures further down are catalog estimates, not a charge, and no mailbox is created or billed.

Picture-day reminders, gallery notices, and receipts under your brand.
Families reach their own child by a permission-checked link, never a face match.
Contact details stay on our own private system, never sold to an outside company.

The actual problem

Picture-day mail is not a deliverability problem. It is a permission problem.

Almost every article about sending mail from your own domain is about getting into the inbox: the records, the warm-up, the bounce rate. Those things matter here too, and this page covers them further down without dressing them up.

But they are not the hard part of this particular lane. The hard part is that the recipient list is a roster of children's guardians, and the message often carries a link to a photograph of a child. Every send has to answer a stack of questions before it is allowed to leave, and it has to answer them again on the next send, because the answers change.

A family withdraws a permission in October. A guardian's address hard-bounces in November. A second guardian is added to a record in January. A program that imported a list in September and mails it in February is mailing a list of facts that were true five months ago. That is the failure mode this lane is built against, and it is the reason the page below is organised around checks rather than around features.

One more thing said plainly at the top, because it changes how you should read everything after it: sending is not live yet. The mail server we run ourselves is built and the code that talks to it is built, but the latch that lets it actually accept a message is off until the endpoint is provisioned in our own network. A message sent today becomes a durable queued row, and the system records it as queued rather than sent. It is never reported as delivered when it was not.

The lane

Five messages, and the only two categories that matter

A picture-day program sends five things. Whether each one is a marketing message or a relationship message decides which gates can stop it, and that classification lives in code rather than in a policy document.

The save-the-date

The picture-day reminder, sent before the camera is set up

Goes to: A guardian whose child has a booked slot.

Built, latched off · Relationship mail — an opt-out cannot silence it

A guardian who booked a slot gets a reminder as the day approaches. It is a relationship message about a booking they made, so it is not a marketing send and does not need a marketing opt-in to reach them. It still passes the address-level suppression stop, because an address that hard-bounced or filed a complaint must not be mailed on any lane at all.

The reminder carries a time and a place. It does not carry a roster, a class list, or another family's name, and it never names a second child.

Defined at packages/notifications/src/types.ts (appointment_reminder)

The follow-up

The follow-up, for a family that has not looked yet

Goes to: A guardian who opted in and has not opened the gallery.

Built, latched off · Marketing — needs an opt-in

The middle step of the same sequence. Same category, same gate, same suppression stop. It exists because a family that misses the first notice usually misses the ordering window too, and a program that cannot follow up loses the order for a reason that has nothing to do with the photograph.

Cadence is data, not a hardcoded delay: the sequence declares its own step offsets, so a program that wants a quieter follow-up changes a number rather than editing a template.

Defined at packages/notifications/src/campaign/presets.ts:123 (portraitDaySequence step 2)

Last call

The last-call notice before the ordering window shuts

Goes to: A guardian who opted in, near the deadline.

Built, latched off · Marketing — needs an opt-in

The closing step. It is the most commercially useful message in the sequence and also the one most likely to annoy, which is exactly why it sits behind the same opt-in as the other two and behind a one-click unsubscribe that works forever.

The unsubscribe target is a signed, stateless token with no expiry, because a recipient may click a link in a year-old message and it must still work. That is a deliberate design decision recorded in the module, not an accident of implementation.

Defined at packages/notifications/src/campaign/presets.ts:123 (portraitDaySequence step 3)

The receipt

The order confirmation, which nothing is allowed to touch

Goes to: A guardian who placed an order.

Built, latched off · Relationship mail — an opt-out cannot silence it

An order confirmation is classified urgent. That has three consequences, all of them enforced in code rather than in a runbook: it sends immediately on every channel the recipient is reachable on, a quiet-hours window cannot defer it, and a per-channel marketing opt-out cannot silence it. A receipt is not a promotion and must not be treated as one.

It is also on the list of message kinds that the in-VPC personalization pass is structurally forbidden from rewriting. A receipt ships verbatim, byte-stable, every time. That exclusion is a constant in the module, not a runtime setting an operator could flip by mistake.

Defined at packages/notifications/src/orchestration/channel-select.ts:75 (URGENT_KINDS)

Of the 5 messages this lane carries, 3 are marketing and can be refused outright by a missing opt-in, and 2 are relationship messages that an opt-out cannot silence.What an opt-out can and cannot stopAn opt-out stops theseMarketing: needs an affirmative opt-in- Gallery is ready- The follow-up- Last callAn opt-out cannotRelationship mail about the family's own action- The save-the-date- The receipt
Figure 1. Generated from the same message table that writes the tabs above, so the split shown here cannot disagree with what each tab says about itself.

The decision chain

7 checks, in order, on every send

None of these is a setting. Each one is a module with a name you can go and read, and each one is evaluated again on the next send rather than remembered from the last one.

A message enters at the top and must pass all 7 checks in order before the mail server is ever asked to accept it. Any check that cannot answer blocks the send.A picture-day message enters here1Is the recipient list a query or a frozen copy?2Marketing needs an affirmative opt-in. Relationship mail does not.3Own child only, and on a shareable surface, first name plus last initial.4A defence-in-depth scan runs after the message is composed.5A hard bounce, a complaint, or a global opt-out stops everything.6Deferrable messages wait. Urgent ones never do.7A retry after a slow acceptance must not become a second email.Only now is the mail server asked to accept it.
Figure 2. The chain, drawn from the same array that writes the panels below. A check that cannot answer blocks the send rather than guessing.
Who is the audience, right now?

Is the recipient list a query or a frozen copy? Built

A list here is criteria, never a stored set of people. The audience is re-derived from the roster on every single send, so a permission changed this morning is honoured this afternoon without anyone remembering to re-import anything.

The shape that makes this safe is in the type: the list specification carries a scope and an anchor and a source subset id, and there is no field a resolved person or address could be smuggled into. You cannot freeze a list you have nowhere to put one.

apps/api/src/services/roster-distribution-lists.ts:256 (resolveListSpecAudience)

Whose child may be named, and how much of the name?

Own child only, and on a shareable surface, first name plus last initial. Built

A picture-day message is about one child and is written to that child's own guardian. The personalization context has room for one child, so there is no shape in which a second child's details could ride along.

Anything that could be forwarded, shared, or seen by someone other than that guardian is reduced to a first name and a last initial before it is written into a message. That reduction is a function, not a convention, so it cannot be forgotten by whoever writes the next template.

packages/notifications/src/privacy.ts:19 (firstNameLastInitial)

Did anything sensitive slip into the body?

A defence-in-depth scan runs after the message is composed. Built

The rendered body is scanned for patterns that must never appear verbatim in a message that touches a minor. This is a backstop and is described as one: it exists because a template author will eventually make a mistake, and a second pair of eyes that never gets tired is cheaper than the incident.

A backstop is not a permission slip. The schema-level rules upstream are the real control; this is the net under them.

packages/notifications/src/privacy.ts:62 (scanForSensitivePii)

Has this address told us to stop?

A hard bounce, a complaint, or a global opt-out stops everything. Built

This check binds to the ADDRESS, not to the campaign, so it is honoured identically on every channel and by every caller, including a one-off send that never went near the planner.

It is fail-closed. If the suppression store cannot answer, the send is blocked. Sending into uncertainty about a complaint address is how a program torches its own deliverability for every other family on the roster.

packages/notifications/src/delivery/suppression.ts:53 (suppressionGuard)

Is now an acceptable hour?

Deferrable messages wait. Urgent ones never do. Built

A promotional or reminder message that lands inside a recipient's quiet-hours window is DEFERRED to the end of that window rather than dropped. Deferring keeps the message; dropping loses it silently, which is the worse failure.

Urgent kinds bypass this entirely. A receipt or an authentication link that waits until morning is a broken product, not a polite one.

packages/notifications/src/orchestration/channel-select.ts:191 (isInQuietHours)

Did we already send exactly this?

A retry after a slow acceptance must not become a second email. Built

Every send derives a stable key from the channel, the address, and the rendered message, so the same logical message always presents the same key however many times the runner attempts it.

A success seals the key; a failure does not. That asymmetry is the whole point: a transient failure stays legitimately retryable, while a message the provider already accepted cannot be sent twice because a socket hiccuped on the way back.

packages/notifications/src/delivery/idempotency.ts:45 (deriveIdempotencyKey)

Two of these deserve to be called out because they fail in the safe direction and that costs something. The suppression stop blocks when it cannot decide, which means a store outage stops mail rather than risking a complaint address. The quiet-hours rule defers rather than drops, which means a deferred message arrives later than an operator might expect. Both are deliberate, and both are the choice we would make again.

The wire

We wrote the mail server dialogue ourselves

The part of this that talks to another organisation's mail server is ours. It opens the session, reads the greeting, offers the introduction, upgrades the connection to an encrypted one when the far side advertises that it can, states the sender and the recipient, sends the message body with the escaping the protocol requires, and closes politely. Then it reads the reply code and files the attempt as delivered, deferred, or bounced.

That matters for a plain commercial reason. A school photo program that hands its family mail to an outside marketing service has handed that service the family list. The whole point of an address on a domain you control, backed by a server the vendor also controls, is that the list has one fewer place to be.

Two honest notes about it. First, every piece of input and output in that dialogue is injected rather than hardcoded, which is why it can be tested against a scripted fake server with no network at all; that is a design property, not a claim about production traffic. Second, a transport built without a real socket factory is not provisioned, and it reports a deferred attempt rather than pretending to have delivered anything. It has no code path that fabricates a success.

Bounces come back to a per-message envelope address rather than to one shared mailbox, so a failure is attributable to a single send instead of smearing across the domain's reputation. That piece is built and running.

packages/notifications/src/mitto-smtp-transport.ts:246 (the session) · :176 (the per-message return path) · :201 (protocol-level body escaping)

Sender authentication

What the records do, and what nobody can promise

The builder that computes the desired records for a sending domain exists and is named below. Publishing them for a particular domain is an operator step, and this page does not claim it has been done for yours.

DKIM Built, latched off

Signs a message with a private key so a receiving server can check it was not altered in transit and really came from this domain.

packages/notifications/src/mitto-dkim-signer.ts:123 (makeMittoDkimSigner)

SPF Built, latched off

Lists which servers are allowed to send for the domain, so a stranger's server cannot claim to be you.

apps/api/src/services/desired-dns-records.ts:34 (sendingDomainDnsRecords)

DMARC Built, latched off

Tells a receiving server what to do when a message fails the two checks above, and asks for a report when it happens.

apps/api/src/services/desired-dns-records.ts:34 (sendingDomainDnsRecords)

Return-path Built

Routes a bounce back to a per-message envelope address, so a failure is attributable to one send rather than to the whole domain.

packages/notifications/src/mitto-smtp-transport.ts:176 (buildVerpEnvelopeFrom)

BIMI Built, latched off

Shows a verified brand mark beside the message in the inboxes that support it. Readiness is reported in tiers and a mark is never dressed up as certified when it is not.

packages/notifications/src/bimi.ts:129 (bimiReady)

No provider on earth can promise inbox placement, and a page that implies otherwise is selling you something it does not control. What is controllable is whether the records are correct, whether the signature is real or absent, and whether a failure is reported honestly. The signing path is built and its key store is empty by default, so today a message is sent unsigned and flagged as unsigned rather than sent with a fabricated signature. When a key is provisioned, the same path signs for real.

Built and not built

The table where we lose two rows on purpose

Product categories, not named vendors. Two rows below go against us today, and they are the two a buyer most needs to know before signing anything.

Every cell states its verdict in words, so the table reads correctly with the stylesheet removed and with no colour perception at all.
Question a buyer should askA bulk marketing toolA school communications appThis lane
The recipient listA file imported onceA synced copy, refreshed on a jobA query, re-derived every send
Consent granularityOne global unsubscribePer-list opt-outPer-category opt-in, checked at plan time
A receipt cannot be suppressed by a promo opt-outDepends on list setupDepends on list setupEnforced by message kind, in code
Who holds the family contact listThe marketing vendorThe marketing vendorOur own private system
The mail server itselfThird partyThird partyOurs, and not yet accepting mail
Sending is live todayYesYesNo -- queued, and recorded as queued
Billing is live todayYesYesNo -- catalog figures only
A face is used to route a messageVaries by productVaries by productNever -- permission-checked links only

If a page shows you a comparison table where it wins every row, the table was built backwards from the conclusion. Two rows here are honest losses: we are not accepting mail yet and we are not billing yet. Both are latches with names, and both are off.

The counts, computed from the tables on this page

What is actually finished

Where AI is, and is not

No AI writes a picture-day message. Here is the whole truth about it.

There is a message-personalization pass in the codebase. It is honest to say it exists, and dishonest to let you infer more than it does, so here is all of it.

What it may touch: a subject line and an opening line. Not a link, not an amount, not legal text, not a call to action, not the rest of the body.

What it may never touch: an order confirmation, a refund, a payout, an authentication link, or a safety block. Those are on a constant list of excluded message kinds, so they ship exactly as written, every time. The exclusion is structural rather than a setting an operator could switch off by accident.

When it runs: only when a caller injects a provider that runs inside our own network. An absent provider, or one that is not in-network, returns the original message unchanged. Every error, guard trip, or detected leak also returns the original message unchanged.

What is running on this lane today: nothing. No provider is wired into this surface. Every message described on this page is a written template with fields filled in, and that is what it will stay until someone decides otherwise on the record.

And to close the question people actually mean when they ask about AI on a school photo product: no face recognition is used anywhere in this lane. A family reaches a gallery through a permission-checked link. There is no face-match step to turn on here, and introducing biometric routing would be a founder decision about handling children's biometric data, not a feature toggle.

packages/notifications/src/ai/personalize.ts:83 (the excluded kinds) · :109 (the in-network-or-skip check) · :184 (the fail-safe pass)

What a mailbox would cost

Catalog figures for planning a budget. Not a price you are charged. Every plan is per mailbox, monthly, with storage included.

Team $2.50 per mailbox / month

Catalog price — not a charge

Includes 5 mailboxes · 25 GB each.

A small set of on-brand school addresses for a department or office.

At the included 5 mailboxes, that is about $12.50 / month at catalog rates — an estimate, never billed.

Organization $2.00 per mailbox / month

Catalog price — not a charge

Includes 25 mailboxes · 50 GB each.

School-wide branded mailboxes with shared routing and archiving.

At the included 25 mailboxes, that is about $50.00 / month at catalog rates — an estimate, never billed.

A single school office usually plans against the team tier. A studio running picture day across a district tends to look at the organization tier. Both figures are catalog, and neither is a charge.

What is true today

Catalog prices, not a charge

We show these figures so a program can plan a budget, not because you can buy right now.

There is no buy button on this page. There is no signup that bills. Turning a mailbox on is a founder-confirmed step and it is currently off.

  • Prices shown are catalog estimates, not a charge -- checkout is not open yet.
  • Mailbox provisioning is a founder-confirmed step and is currently off; no mailbox is created and no card is billed.
  • A branded address is a professional mailbox, not a claim that any specific school or studio uses it.

Nothing on this page creates a mailbox or moves a cent. When that changes, this page will say so in the same plain words it uses now.

Boundaries

What this is not

A page that only lists what a product does is half a page. These are the things a buyer will otherwise assume, and each one is a no.

Not a bulk campaign tool

There is no blast scheduler, no list builder that mines a roster, and no open-rate leaderboard here. The sequence steps are declared as data because a program needs to change a cadence, not run a growth experiment on families.

Not a gallery

This is the mail lane. Where the photographs live, who may see them, and how an order is placed are different surfaces with their own pages. The mailbox is the channel, not the archive.

Not a face-match product

No face is compared to anything to decide who gets a message or who may open a link. Permission decides. Adding biometric routing would be a founder decision about children's biometric data, not a toggle.

Not a live sender yet

The owned mail server's accept latch is off. A send today becomes a durable queued row and is recorded as queued. Nothing reports a delivery that did not happen.

Not a deliverability guarantee

Records can be correct and a signature can be real, and a receiving server may still route a message somewhere you did not want. We describe what the records do and stop there.

Not an archive migration

There is no automated import from an old account. Forwarding from the old address plus a note on the way out is the honest first move, and we will say that rather than sell a migration we have not built.

Questions

What a program manager asks first

Can I send today?

No. The owned mail server's accept latch is off until the endpoint is provisioned in our own network. A send becomes a durable queued row and the system records it as queued, not sent. We would rather tell you that than show you a green tick.

If a family withdraws permission after I schedule a send, what happens?

The audience is re-derived from the roster at send time rather than read from a saved copy, so the withdrawal is honoured on the very next send with nobody re-importing anything.

Will an opt-out stop a family from getting their receipt?

No. A receipt is classified as a relationship message, not a promotion, and a promotional opt-out cannot silence it. That classification is in code, not in a setting on your account.

Do you use face recognition to send a family their own child's gallery?

No. A family reaches their own child through a permission-checked link. There is no face-match step in this lane at all.

Who can see the family contact list?

It stays inside our own private system rather than being handed to an outside marketing company, and it is not sold. We do not claim it is untouchable or that it can never leave; we run the system ourselves and keep the handling tight, and that is the honest version.

What happens if the suppression store is down when I send?

The send is blocked. That check fails closed on purpose: mailing into uncertainty about a complaint address risks the deliverability of every other family on your roster.

Does a message get sent twice if the network hiccups?

It should not. Each send derives a stable key from the channel, the address, and the rendered message, and a success seals that key so a retry returns the earlier result instead of sending again. A failure does not seal it, so a genuine failure stays retryable.

Is an unsubscribe link going to expire?

No, and that is deliberate. The token carries no expiry because someone may click a link in a year-old message, and an unsubscribe that has quietly stopped working is worse than no unsubscribe at all.

Are my prices going to be what is shown here?

We do not know, and neither does anyone else yet. Those are catalog figures for planning. Checkout is not open, nothing is billed, and the numbers exist so a budget conversation can happen.

Can you migrate my existing mailbox?

Not automatically. We have not built an import, and saying otherwise would be the easiest sentence on this page to write and the fastest one to be caught on.

Asking a question

There is nothing to sign up for on this page, so the only thing to do here is ask something. A real answer comes back, including when the answer is that we have not built the thing you asked about.