CommunicationCoordinator is the shared outbound-email transport for CodeNforce — every
subsystem that sends mail (CEAR confirmations/blasts, the Letters subsystem's
EMAIL_CNF_API channel) goes through it, and it also owns the one piece of inbound
infrastructure: verifying signed delivery-status webhooks from Resend.
Loaded once at CDI startup (initBean() → loadEmailConfig()) from
${jboss.server.config.dir}/codenforce.properties:
| Property | Purpose |
|---|---|
resend.environment |
test (default) or prod. In test, sendEmail() writes the message to stdout instead of calling the Resend API — no live send, no API key required. |
resend.api.key |
Resend API key, only used when resend.environment=prod. |
resend.from.address / resend.from.name |
Default sender identity, applied by getEmailSkeleton(). |
resend.webhook.secret |
Svix/Resend webhook signing secret (whsec_..., from resend.com/webhooks). Blank → isResendWebhookConfigured() is false and every webhook call is rejected — fail-closed, not fail-open. |
tcvce.cear.optout.url |
Base URL used to build the unsubscribe link in CEAR update-blast emails. |
If the properties file can't be found or read at all, the coordinator falls back to
testMode = true rather than throwing — a missing/misconfigured file degrades to
stdout-only logging instead of taking the app down.
getEmailSkeleton() — factory returning an EmailMessage pre-populated with theto/subject/htmlBody.sendEmail(msg, ua, muni, refType, refId, category) — validates required fieldsvalidateEmailMessage), then either logs to stdout (test mode) or callscallResendApiWithResult(), which never throws — any ResendException/generallogging.emaillog rowEmailLog, via SystemIntegrator.insertEmailLog), discriminated byEmailCategoryEnum (CEAR_CONFIRM_PUBLIC, CEAR_CONFIRM_INTERNAL,CEAR_STAFF_SUBSCRIBE, CEAR_ROUTING_UPDATE, CEAR_UPDATE_BLAST,LETTER_EMAIL_DELIVERY, LETTER_DISTRIBUTION_RULE_NOTIFICATION) and tagged withrefObjectType/refObjectId (e.g. "CEACTIONREQUEST" + the CEAR's id) so any send canEmailLog.resendMessageId — this is the join key the inbound webhook later uses to findcomm_getAllEmailLogs(ua) exposes a raw, sysadmin-only dump of every emaillog rowAuthorizationException otherwise) — the table carries addresses,All of these render inline-styled HTML via j2html (cear_buildConfirmationEmailHTML,
cear_buildRoutingUpdateEmailHTML, comm_buildCEARUpdateBlastHTML) and are non-throwing —
individual recipient failures are logged to stderr and accumulated into notified/failed
lists rather than aborting the whole blast:
sendCEARSubscriptionBlast) — sent once, right after acearSubscribeEnabled=true.sendCEARRoutingUpdateBlast) — sent when a CEAR is routed, tocear.isGetEmailUpdates()=true.comm_resendConfirmationToTargets, comm_resendStatusUpdateToTargets)comm_getMuniUpdateBlastSettings,comm_saveMuniUpdateBlastSettings) — a JSONB-backed, per-event-type configurationCEARUpdateBlastEventTypeEnum: CEAR_ROUTED, NOV_MARKED_SENT, VIOLATION_ATTACHED,VIOLATION_MARKED_COMPLIANT, VIOLATION_NULLIFIED, VIOLATION_STIPCOMP_EXTENDED,CASE_CLOSED, CASE_EVENT_NCM, CITATION_STATUS_NCM) letting each muni independentlycomm_sendCEARUpdateBlast) — the central gate for everyMINIMAL / STANDARD /FULL) down from FULL to STANDARD for any submitter who isn't internal staff, sinceFULL can surface internal case specifics.writeBlastTraceNote) recording exactly which addresses were notified and which failedResend calls back into CodeNforce as delivery events happen for a sent message (delivered,
bounced, opened, clicked, complained, delayed). CommunicationCoordinator owns signature
verification for this; a JAX-RS resource owns receiving the HTTP call and applying the
result.
Currently wired up for Letters only. The receiver endpoint
(LetterEmailWebhookResource,POST /api/webhooks/resend/letter) resolves the event back
to aLetterDistributionEntryand is only reachable for emails logged under the
LETTER_EMAIL_DELIVERYcategory. CEAR emails are logged toEmailLogthe same way and
carry aresendMessageId, but nothing currently subscribes their delivery status back
onto the CEAR — see BL-4 on the Letters backlog for
the one known follow-on (ContactEmail.bouncedtsisn't written for CEAR bounces either).
The signature-verification method itself is generic and reusable by any future receiver.
Full walkthrough of the letter-specific receive/apply logic (event mapping, idempotency,
LetterDistributionEntry update) lives on the Letters subsystem's own page:
Distribution channels and webhooks.
The rest of this page focuses on the signature-verification mechanism itself, since it's
shared infrastructure and the part most likely to be reused or gotten subtly wrong.
comm_verifyResendWebhookSignature — what it actually checkspublic boolean comm_verifyResendWebhookSignature(byte[] rawBody, String svixId,
String svixTimestamp, String signatureHeader)
rawBody/svixId/svixTimestamp/signatureHeader is null or blank.svixTimestamp as unix seconds and rejects if it's unparseable, or if it's moreWEBHOOK_TIMESTAMP_TOLERANCE_SECONDS (300s) away from the server's current time —whsec_ prefix and base64-decodes the remainder to get the raw HMAC<svix-id>.<svix-timestamp>.<raw body> and computesv1,<base64sig> tokens and compares eachMessageDigest.isEqual (constant-time,The caller (LetterEmailWebhookResource) captures the request body as byte[], never
String, and only parses it with Jackson after verification succeeds — no JSON
provider or charset round-trip is allowed to touch the bytes the signature was computed
over, and parsing before verification would hand an attacker a signature-bypass oracle.
This section explains the two pieces of jargon in
comm_verifyResendWebhookSignature's javadoc — "Svix envelope" and "bare HMAC" — for anyone
who hasn't worked with signed webhooks before.
HMAC (Hash-based Message Authentication Code) is a standard construction that combines a
cryptographic hash function (here, SHA-256) with a secret key to produce a short tag that
proves two things at once: the message wasn't altered, and the sender knew the shared
secret. Only someone who has the key can produce a tag that verifies — that's what makes it
usable for authenticating a webhook call instead of just detecting corruption.
A "bare HMAC of the body" would be the simplest possible scheme: compute
HMAC(secret, rawBody) and send that tag in a header; the receiver recomputes it over the
body it received and compares. This is a real scheme some providers use — but on its own it
has a gap: if an attacker ever captures one legitimate request (network log, misconfigured
proxy, browser history on a webhook-testing tool), that exact body+signature pair remains
valid forever, since nothing about the signed content ever changes. The attacker can
replay it any time.
Svix is a webhook-sending platform; Resend uses Svix's conventions
for its own webhooks (hence the Svix-Id / Svix-Timestamp / Svix-Signature header
names even though the caller is Resend). The envelope is the extra structure Svix wraps
around the raw payload before signing, specifically to close the bare-HMAC replay gap above:
svix-id — a unique id for this specific delivery attempt.svix-timestamp — unix seconds when the event was sent.This buys two properties a bare-body HMAC can't:
svix-ids, so the signed contentThe signature header itself is a space-delimited list of v1,<base64> tokens (versioned so
future signature schemes can be introduced without breaking old receivers), and providers
that support secret rotation send multiple valid tokens during the rotation window — the
receiver just needs any one of them to verify, which is why
comm_verifyResendWebhookSignature loops over every token instead of checking only the
first.
LetterDistributionEntry update logic.communication).Back to: Communication — Developer Notes