Add encryption policy, split delivery and SecureMailSender

- delivery: PolicyResolver (address > *@domain > default), RecipientKeyResolver
  (discovery + validation), DeliveryPlanner (REQUIRE refusal, SPLIT/FAIL,
  own copy per Bcc with hidden key IDs), MessageAssembler with sender keys
  for readable sent copies, EnvelopeMimeMessage for per-copy SMTP envelopes
- sender: SecureMailSender with BUILT_IN and RELAY implementations and a
  per-recipient SendReport; relay passes the strictest policy as a header hint
- autoconfigure: mode, default-policy, mixed-mode, recipients, sender-keys
  and relay properties; relay sender is not a bean so spring.mail stays intact
- demo: sends through SecureMailSender

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G71TQ5jDETVwdUJxGAwaiT
This commit is contained in:
Claude committed 2026-10-12 03:27:40 +00:00
1 parent a788f7a48a
commit 3bce057eab
34 files changed
+1562 -114

No files matched your search

+30 -4
View File
@@ -19,12 +19,38 @@ mvn verify
| `mailsender-pgp-spring-boot-starter` | dependency bundle for applications |
| `mailsender-demo` | sample app that sends test mails |
## Usage
Add `pro.thinhha.mailsender:mailsender-pgp-spring-boot-starter`, configure `spring.mail.*` as usual, and send
through `SecureMailSender` instead of `JavaMailSender`:
```java
SendReport report = secureMailSender.send(message); // MimeMessage or SimpleMailMessage
```
Each recipient's key is looked up via WKD (cached) and validated. The message is then sent PGP/MIME-encrypted to
recipients with a usable key and in plaintext to the rest (logged as a downgrade), or refused with
`EncryptionPolicyException` if a `REQUIRE` recipient has no key. `SendReport` lists the outcome per recipient.
```yaml
mail:
pgp:
mode: BUILT_IN # or RELAY: hand plaintext to an encrypting gateway (mail.pgp.relay.*)
default-policy: OPPORTUNISTIC # REQUIRE | OPPORTUNISTIC | PLAINTEXT
mixed-mode: SPLIT # or FAIL
recipients:
"[*@partner.example]": REQUIRE
sender-keys: # your own PUBLIC key, so sent copies stay readable
"[noreply@example.com]": file:/etc/mail/noreply.pub.asc
```
Map keys that are addresses need the `[...]` brackets. See [docs/PLAN.md](docs/PLAN.md) for all options.
## Demo app
The demo app looks up and validates the recipient's key via WKD, then sends `DEMO_COUNT` test mails
(default: 1 to `test@thinhha.pro`). With a valid key the mail is PGP/MIME-encrypted (RFC 3156); otherwise it is
sent in plaintext and a warning is logged. The policy layer (REQUIRE / OPPORTUNISTIC / PLAINTEXT, split
delivery) comes in milestone 5.
The demo app sends `DEMO_COUNT` test mails (default: 1 to `test@thinhha.pro`) through `SecureMailSender` and logs
the outcome per recipient: PGP/MIME-encrypted (RFC 3156) when a valid WKD key is found, otherwise plaintext.
Set `MAIL_PGP_MODE=RELAY` and `RELAY_HOST`/`RELAY_PORT`/`RELAY_USERNAME`/`RELAY_PASSWORD` to send via a gateway.
```
mvn -q install -DskipTests