Videre fra Del 1#
Del 1 dækkede grundstrukturen: domænemodellen, AI-integrationen og de primære views. Alt det fungerede som en prototype.
Del 2 handler om hvad der sker, når man tager prototypen et skridt videre — og hvad man opdager, når man viser den til en rigtig bruger.
Rigtig e-mail-afsendelse#
Prototypen loggede e-mails til konsollen. Det var fint til demo — men for at systemet er nyttigt, skal mails faktisk afsendes.
Vi integrerede JavaMail via SMTP. Konfigurationen holdes i miljøvariabler:
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=koordinator@engestofte.com
SMTP_PASSWORD=...EmailService-klassen wrapper JavaMail og tager en simpel DTO:
public record EmailRequest(String to, String subject, String body) {}Den del var teknisk ukompliceret. Det interessante var, hvad det tvang os til at tænke over: hvem ejer afsendelsen?
I prototypen genererede AI’en teksten og systemet “sendte” den med det samme. I den rigtige version er der et trin imellem: admin reviewer teksten, redigerer den hvis nødvendigt, og bekræfter afsendelse. Det er en bevidst designbeslutning — systemet forbliver et redskab, ikke en autonom afsender.
Det betød at vi tilføjede en AFVENTER_REVIEW-status i emaillog-tabellen, og at send-knappen i frontenden er deaktiveret, inden admin aktivt har åbnet preview-modalen og trykket “Godkend og send”.
Automatiske påmindelser#
Tre scenarier kræver automatisk opfølgning:
- Kontrakt sendt men ikke underskrevet efter 5 dage
- Faktura sendt men ikke betalt inden forfaldsdatoen
- Ingen respons på initial kontakt-e-mail efter 7 dage
Vi løste det med et simpelt cron-job, der kører dagligt ved midnat:
@Scheduled(cron = "0 0 0 * * *")
public void kørPåmindelser() {
List<Virksomhed> udløbteKontrakter = virksomhedRepo
.findAlleUnderskriftForsinkede(LocalDate.now().minusDays(5));
for (Virksomhed v : udløbteKontrakter) {
String tekst = aiService.genererPåmindelsesEmail(v, "kontrakt");
emailLog.gem(v, tekst, EmailType.PÅMINDELSE);
}
}Påmindelserne lægges i e-mail-log’en til review — de sendes ikke automatisk. Det er det samme princip som ved den initiale kontakt: AI genererer, menneske godkender.
Spec-driven API-design#
Da vi skulle udvide API’et med de nye endpoints (e-mail-afsendelse, påmindelser, faktura-opdatering), valgte vi at skrive API-spec’en før vi implementerede.
Det lød tungt. Det tog 45 minutter.
Vi brugte en simpel markdown-fil med følgende format for hvert endpoint:
### POST /api/virksomheder/{id}/send-email
**Formål:** Afsend en godkendt e-mail til stadeholderen.
**Request body:**
{
"emailLogId": "uuid",
"subject": "string"
}
**Response 200:**
{ "sent": true, "timestamp": "ISO-8601" }
**Response 400:** EmailLog ikke i status AFVENTER_REVIEW
**Response 404:** Virksomhed eller EmailLog ikke fundetDet tvang os til at tage stilling til fejlscenarier og responser, inden vi kodede. Et eksempel der betalte sig: vi opdagede at vi manglede en subject-parameter i request body — e-mails har emnelinjer, og det var ikke med i vores oprindelige design.
Spec’en blev brugt direkte som input til AI-assisteret kodegenerering af controller-metoderne. Det virkede godt — Claude genererede korrekte signaturer og fejlhåndtering første gang, fordi spec’en var præcis.
Første brugertest#
Vi viste systemet til en person med erfaring fra admin-arbejde. Ikke en tekniker. Det var lærerigt.
Hvad der fungerede#
Hun var hurtig til at forstå status-flowet og fandt farveindikatorerne på statusserne intuitive. E-mail-preview-modalen med inline redigering var den funktion hun roste mest — “det er præcis sådan jeg vil have det, jeg kan se hvad der sendes, inden det sendes.”
Hvad der ikke fungerede#
Terminologi. Vi havde brugt vores egne navne internt: “Oprettet”, “Kontaktet” osv. Hun forstod “FAKTURA_SENDT” men var forvirret over forskellen på “KONTRAKT_SENDT” og “KONTRAKT_UNDERSKREVET” — i praksis er der dage eller uger imellem dem, men de lød næsten ens.
Løsning: vi tilføjede en kort beskrivelse under hvert status-badge (“Venter på signatur”) og ændrede farven på “KONTRAKT_SENDT” til gul (afventer) frem for grøn (færdig).
Tabellen var for bred. Med alle kolonner synlige på en standardlaptop skærmstørrelse, var tabellen horisontal scrollbar. Hun scrollede ikke — hun troede det var alt. Vi fjernede tre kolonner fra default-visningen og flyttede dem til detail-view.
Ingen fortrydelse. Da hun ved en fejl klikkede “Send faktura” på en virksomhed der ikke var klar, var der ingen undo. Vi tilføjede en bekræftelsesdialog til alle irreversible handlinger.
Hvad der mangler#
Systemet er tæt på brugbart til et internt pilotforsøg. De resterende mangler:
| Feature | Prioritet | Kommentar |
|---|---|---|
| Digital kontraktsignering | Høj | PDF + manuel scanning er stadig manuelt |
| Batch-handlinger | Middel | Vælg 10 virksomheder, send alle e-mails på én gang |
| Dashboard-statistik | Lav | Antal bekræftede, forventet omsætning |
| Autentificering | Høj | Systemet er i dag åbent — skal have login inden det går live |
Autentificeringen er den mest kritiske. Det er en intern app til én organisation, så vi planlægger at bruge en simpel email+password-login med JWT — ikke OAuth, ikke magic links, ikke Google SSO. Det passer til use-casen.
Refleksioner#
To ting stod tydelige efter del 2:
Spec-first betaler sig, selv til interne projekter. Det tager 30–60 minutter, men det fjerner tvivlspørgsmål undervejs og giver AI-assisteret kodegenerering et meget bedre udgangspunkt.
Brugertest afslører andet end teknikken. Den største gevinst fra brugertesten var ikke fejl i koden — det var fejl i terminologi og layout. Det er ting, der ikke vises i unit tests og ikke opdages ved code review.
Næste milestone: tilføje autentificering, teste systemet internt med et rigtigt datasæt og evaluere om outputtet fra AI-generering er godt nok til faktisk brug — eller om vi skal skifte model.