LetterDistributionEntry (Phase 15.1, table letterdistributionentry, renamed from
lettermailingattempt) is the single row type for every way a finalized letter is
distributed — mail, email, posting/placard, hand delivery, or a door-hanger courtesy notice.
One append-only history per letter, one entity, channel-specific fields left null when
irrelevant.
LetterDistributionMethod — the channel enumpublic enum LetterDistributionMethod {
FIRST_CLASS_MAIL, CERTIFIED_MAIL, HAND_DELIVERY,
DOOR_HANGER, POSTING_PLACARD, EMAIL_NON_CNF, EMAIL_CNF_API;
public enum Kind { PHYSICAL_MAIL, PHYSICAL, ELECTRONIC }
}
Each value carries: label, kind, requiresTrackingNumber (certified mail only),
requiresPhysicalPresence (drives whether the UI offers evidence-photo capture), and an
audit/attributability axis (II.E) — three booleans that answer "who logged this and can
it be undone":
userAttributable — true for every channel except EMAIL_CNF_API. A user-attributablecreatorCanDeacSameDay / managerCanDeacSecondDayOrLater — who can deactivate aEMAIL_CNF_API is the one non-user-attributable channel — an automated systemDOOR_HANGER vs. POSTING_PLACARD are kept as separate channels (not one "posting" channel
with a sub-type flag) because their legal weight differs: a door-hanger is a courtesy notice,
a placard is an IPMC-specified posting with conspicuous-visibility requirements — different
default templates apply.
LetterCoordinator.letter_recordMailingAttempt(...) (mail/hand-delivery, via the
mark-sent dialog) and letter_distributeByPosting(...) (posting/placard/door-hanger, via
the record-posting dialog) both write a LetterDistributionEntry directly — no external
service involved. letter_distributeByPosting additionally handles the optional evidence
photo: when photo bytes are supplied, the blob is inserted and linked to the letter
(via BlobCoordinator.insertBlobAndInsertMetadataAndLinkToParent) before the distribution
entry is written, and the returned photodoc id is stamped onto
distributionEvidencePhotodocId — the composite FK
letterdistributionentry_distevidence_fk guarantees the photo actually belongs to that
letter. A photo is "rich-optional" (D4): never required, strongly encouraged for placards.
The amendment/supersede model carries over from the original mailing-attempt design: any
channel's entry can be superseded by an amendmentOfEntryId-linked amendment rather than
edited in place, and any entry can be soft-deactivated (deactivatedTs) — the details
dialog's "show superseded records" / "show deactivated records" checkboxes control whether
these hidden-by-default rows are shown.
letter_distributeByEmail()Sends the finalized letter via the Resend API and logs a EMAIL_CNF_API distribution entry:
lockedAndQueuedTs != null) with non-emptyrenderedHtml, and a non-blank recipient address must be supplied (alreadyCommunicationCoordinator.sendEmail(...), which never throws on an API-levelEmailLog instead, so a Resend outage doesn'tLetterDistributionEntry with distributionMethod = EMAIL_CNF_API, linkingemailLogId to the just-created log row, and an initial emailDeliveryStatus of "sent""failed" based on whether the log recorded a Resend error.CommunicationCoordinator (also shared with the CEAR emailing subsystem) loads
resend.api.key, resend.from.address/resend.from.name, and resend.environment from
codenforce.properties at CDI startup; resend.environment=test writes emails to stdout only
(no live API call) — see CommunicationCoordinator.loadEmailConfig().
Resend calls back into CodeNforce as delivery events happen (delivered, bounced, opened,
clicked, complained, delayed) via a webhook — this is what keeps
LetterDistributionEntry.emailDeliveryStatus current without polling.
Endpoint: POST /api/webhooks/resend/letter — LetterEmailWebhookResource (JAX-RS,
@Path("/webhooks") + /resend/letter).
Resend delivers webhook signatures via the Svix envelope format. This is easy to get
subtly wrong, so the exact scheme (CommunicationCoordinator.comm_verifyResendWebhookSignature)
is worth stating precisely:
byte[], neverString — no JSON provider or charset round-trip is allowed to touch the bytes before the<svix-id>.<svix-timestamp>.<raw body> — three parts joined with., not the raw body alone.whsec_ prefix andsvix-timestamp must be within WEBHOOK_TIMESTAMP_TOLERANCE_SECONDSSvix-Signature first, falling back to Resend-Signature ifv1,<base64sig> tokens; any oneThe resource always returns 200 for events it processed or safely ignored (so Resend
doesn't retry non-actionable payloads), 401 only on signature failure, and 400 on an
unparseable body.
LetterCoordinator.letter_processResendDeliveryEventpublic boolean letter_processResendDeliveryEvent(String eventType, String resendMessageId)
mapResendEventToStatus(eventType) normalizes the Resend event typeemail.delivered → delivered, email.bounced → bounced, email.complained →complained, email.opened → opened, email.clicked → clicked,email.delivery_delayed → delayed; anything else → unhandled, returns false).EmailLog by Resend message id (SystemIntegrator.getEmailLogByResendMessageId),LetterDistributionEntry by that log's idLetterIntegrator.getDistributionEntryByEmailLogId).LetterIntegrator.updateEntryEmailDeliveryStatus).The method is idempotent — re-applying the same event just re-writes the same status and
timestamp, since Resend (like most webhook providers) does not guarantee exactly-once
delivery.
See also: Clone/copy architecture,
Mail-merge token implementation.
Back to: Letters & Emailing — Developer Notes