Den mest kostbare proces i en virksomhed er sjældent den der tager flest timer - det er den der tager to minutter, 40 gange om dagen, og som ingen nogensinde har sat spørgsmålstegn ved. Manuelle dataoverførsler, kopi-indsæt mellem systemer, opfølgningsmails sendt fra et regneark. Det er præcis de opgaver automatisering er skabt til.
Men ikke alle processer fortjener at blive automatiseret, og ikke alle automatiseringsopgaver fortjener Laravel. Denne artikel gennemgår begge spørgsmål med tre konkrete eksempler fra den virkelige verden - med kode, arkitektur og de faldgruber der rammer første gang.
Hvilke processer er gode automatiseringskandidater?
En proces er en god kandidat til automatisering, når den er regelbaseret, gentagen og forudsigelig. Hvis du kan beskrive den som "hver gang X sker, skal Y gøres med Z data", er den automatiserbar. Hvis den kræver menneskelig vurdering, kreativitet eller undtagelseshåndtering der ikke kan beskrives i regler, er den det ikke.
De bedste kandidater deler typisk disse kendetegn:
Høj frekvens med lav variation - Opgaven udføres mange gange og følger det samme mønster. Ordrebogholderi, statusopdateringer, notifikationer og datasynkronisering mellem systemer er klassiske eksempler.
Klare fejlkriterier - Du kan definere præcist hvad der er en fejl, og hvad systemet skal gøre ved den. En ordre der ikke kan bogføres skal logges og markeres - ikke blot ignoreres.
Tidsfølsom eller forsinkelseskritisk - Processer der skal ske hurtigt eller på et bestemt tidspunkt er svære at håndtere manuelt i skala.
Processer der ikke egner sig: onboarding-flows med nuancerede kundedialoger, kreativ indholdsproduktion, komplekse forhandlinger eller enhver opgave hvor kontekst og skøn er afgørende. Automatiser aldrig noget du ikke fuldt ud forstår - du foreviger bare fejlen hurtigere.
Eksempel 1: Synkronisering af ordredata fra webshop til regnskabssystem
Problemet
En webshop modtager 50-200 ordrer om dagen. Regnskabsafdelingen skal manuelt oprette fakturaer i e-conomic eller Billy. Det tager 3-5 minutter per ordre, fejlraten er ikke ubetydelig, og det sker altid for sent fordi medarbejderen har andre opgaver. Løsningen er en integration der sender ordredata til regnskabssystemets API i det øjeblik en ordre bekræftes.
Løsningen i overordnede træk
Laravel lytter til en order-event (fra webshop-platformen via webhook eller intern event), placerer et job i en kø, og jobbet kalder regnskabssystemets API med de rette data. Queues sikrer at et midlertidigt API-nedbrud ikke taber ordren - den forsøges igen automatisk.
// App\Jobs\SyncOrderToAccountingSystem.php
class SyncOrderToAccountingSystem implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 5;
public int $backoff = 60; // sekunder mellem forsøg
public function __construct(private Order $order) {}
public function handle(AccountingClient $client): void
{
$payload = [
'customer' => $this->order->customer->toAccountingArray(),
'lines' => $this->order->lines->map->toAccountingLine()->toArray(),
'currency' => $this->order->currency,
'reference' => $this->order->reference,
];
$response = $client->createInvoice($payload);
$this->order->update([
'accounting_id' => $response['id'],
'synced_at' => now(),
]);
}
public function failed(Throwable $e): void
{
Log::error('Ordre-synkronisering fejlede', [
'order_id' => $this->order->id,
'error' => $e->getMessage(),
]);
// Notificer regnskabsafdelingen via mail eller Slack
Notification::route('mail', config('accounting.alert_email'))
->notify(new OrderSyncFailedNotification($this->order, $e));
}
}Faldgruber første gang: Den hyppigste fejl er at håndtere API-fejl for optimistisk. Mange implementeringer logger fejlen men glemmer at markere ordren som usynkroniseret - hvilket betyder at regnskabsafdelingen aldrig opdager hullet. Brug altid failed()-metoden aktivt. En anden klassiker er at sende duplikater fordi webhook'en leverer samme event to gange - implementer idempotens ved at tjekke om accounting_id allerede er sat.
Eksempel 2: Automatisk opfølgningsmail til kunder der ikke har gennemført en handling
Problemet
En SaaS-virksomhed ser at 40% af nye brugere aldrig fuldfører opsætningen af deres konto. Ingen mail sendes. Ingen opfølgning sker. Brugeren forsvinder. Det manuelle alternativ - at en medarbejder gennemgår listen dagligt og sender mails - skalerer ikke og sker inkonsekvent.
Løsningen i overordnede træk
En scheduled task kører en gang i døgnet, finder alle brugere der oprettede en konto for mere end 48 timer siden men ikke har gennemført opsætningen, og sender en målrettet opfølgningsmail til dem der endnu ikke har modtaget en.
// App\Console\Commands\SendOnboardingFollowUp.php
class SendOnboardingFollowUp extends Command
{
protected $signature = 'users:onboarding-followup';
public function handle(): void
{
User::query()
->whereNull('onboarding_completed_at')
->whereNull('followup_sent_at')
->where('created_at', '<=', now()->subHours(48))
->where('created_at', '>=', now()->subDays(7)) // Ikke brugere der er for gamle
->chunkById(100, function ($users) {
foreach ($users as $user) {
Mail::to($user)->queue(new OnboardingFollowUpMail($user));
$user->update(['followup_sent_at' => now()]);
}
});
}
}
// I App\Console\Kernel.php
$schedule->command('users:onboarding-followup')->dailyAt('09:00');
Mailen selv bygges som en Mailable-klasse med en Blade-template der personaliserer indholdet baseret på hvad brugeren faktisk har og ikke har gjort - ikke en generisk "du mangler noget"-mail.
Faldgruber første gang: At glemme followup_sent_at-kolonnen og dermed sende den samme mail dagligt til alle inaktive brugere. Endnu en klassiker er at køre kommandoen direkte i stedet for at queue mailsene - ved 500 brugere blokerer det scheduleren i minutvis. Brug altid Mail::queue() eller dispatch jobs til en kø. Vær også opmærksom på tidszoner: dailyAt('09:00') kører i serverens tidszone, ikke brugerens.
Eksempel 3: Import og behandling af leverandørdata via SFTP og CSV
Problemet
En grossist modtager hver nat en CSV-fil fra sin leverandør via SFTP med opdaterede lagerstatus og priser på 15.000 produkter. En medarbejder downloader filen manuelt, renser den i Excel og importerer den. Det tager halvanden time og sker ikke altid - hvilket betyder at webshoppens priser og lagerstatus er forældede.
Løsningen i overordnede træk
En scheduled task forbinder til SFTP-serveren, downloader den nyeste fil, validerer dataene og behandler dem i chunks for at undgå memory-problemer. Fejl logges per linje - ikke som en samlet fejl der stopper hele importen.
// App\Jobs\ProcessSupplierImport.php
class ProcessSupplierImport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
public int $timeout = 600; // 10 minutter
public function handle(SftpService $sftp): void
{
$localPath = $sftp->downloadLatestFile(
remote: '/exports/products/',
pattern: 'products_*.csv',
localDir: storage_path('imports')
);
$importLog = ImportLog::create([
'filename' => basename($localPath),
'started_at' => now(),
]);
$processed = 0;
$errors = [];
LazyCollection::make(function () use ($localPath) {
$handle = fopen($localPath, 'r');
$headers = fgetcsv($handle, 0, ';');
while ($row = fgetcsv($handle, 0, ';')) {
yield array_combine($headers, $row);
}
fclose($handle);
})->chunk(250)->each(function ($chunk) use (&$processed, &$errors, $importLog) {
foreach ($chunk as $row) {
try {
$validated = $this->validate($row);
Product::updateOrCreate(
['sku' => $validated['sku']],
[
'price' => $validated['price'],
'stock' => $validated['stock'],
'synced_at' => now(),
]
);
$processed++;
} catch (ValidationException $e) {
$errors[] = ['row' => $row, 'error' => $e->getMessage()];
}
}
});
$importLog->update([
'processed' => $processed,
'error_count' => count($errors),
'errors' => $errors,
'completed_at' => now(),
]);
if (count($errors) > 50) {
// Mere end 50 fejl er et tegn på et strukturproblem - notificer
Notification::route('mail', config('imports.alert_email'))
->notify(new ImportAnomalyNotification($importLog));
}
}
private function validate(array $row): array
{
// Valider at SKU ikke er tom, pris er numerisk, etc.
if (empty($row['sku'])) throw new ValidationException('SKU mangler');
if (!is_numeric($row['price'])) throw new ValidationException('Ugyldig pris: ' . $row['price']);
return [
'sku' => trim($row['sku']),
'price' => (float) $row['price'],
'stock' => (int) ($row['stock'] ?? 0),
];
}
}Faldgruber første gang: At loade hele CSV-filen i hukommelsen på én gang. En fil med 15.000 rækker er håndterbar, men ved 150.000 rækker crasher processen. Laravels LazyCollection løser dette elegant ved at streame data i stedet for at buffere det. En anden fejl er at bruge en enkelt database-transaktion for hele importen - ved fejl mister du alt. Chunk-baseret behandling med individuel fejlhåndtering per række er langt mere robust. Endelig: tjek altid om SFTP-filen faktisk er ny ved at sammenligne filnavn eller timestamp med den senest importerede fil.
Laravel eller no-code - hvornår er det den rigtige beslutning?
No-code-værktøjer som Zapier, Make (tidligere Integromat) og n8n er legitime løsninger - de er hurtige at sætte op og kræver ingen udviklere til vedligeholdelse. Men de har klare begrænsninger der bliver tydelige i de rigtige scenarier.
Vælg et no-code-værktøj når: Integrationen er simpel og lineær (A sker, send til B), datamængden er lav, fejlhåndtering kan være manuel, og løsningen skal op at køre hurtigt uden udviklertimer.
Vælg Laravel når:
Datamængden er stor - No-code-platforme har operationsgrænser og er dyre i skala. Chunk-baseret behandling af 50.000 rækker CSV er ikke noget Zapier er bygget til.
Forretningslogikken er kompleks - Hvis synkroniseringen kræver sammensætning af data fra tre systemer, betingede transformationer og domænespecifik validering, er no-code-flows hurtigt uoverskuelige.
Fejlhåndtering er kritisk - Retry-logik, dead-letter queues, per-række-fejllog og aktive notifikationer er svære at implementere ordentligt i visuelle flow-editors.
Integrationen er en del af et større system - Hvis du allerede har en Laravel-applikation, er det naturligt at holde automatiseringslogikken der. Det giver bedre testbarhed, versionshistorik og adgang til applikationens domænemodeller.
Tommelfingerreglen: brug no-code til at validere at en integration overhovedet giver mening, og flyt den til Laravel når du rammer begrænsningerne - eller spring direkte til Laravel hvis du ved fra starten at kompleksiteten er høj.
De tre eksempler herover - ordresynkronisering, opfølgningsmails og leverandørimport - repræsenterer de mest almindelige automatiseringsopgaver danske virksomheder sidder med. Fælles for dem er at de alle er løst med standardkomponenter i Laravel: Queues, Scheduler, HTTP-klienten, Mail-facaden og LazyCollections. Ingen eksotiske pakker, ingen magi. Det er netop det der gør dem vedligeholdbare om to år, når den person der implementerede dem ikke længere er i virksomheden.
Har du processer der matcher mønsteret - gentagne, regelbaserede, tidskrævende - er automatisering sjældent et spørgsmål om om, men om hvornår. Jo længere du venter, jo flere timer er allerede spildt. Se mere om hvad automatisering og integrationer kan løse, eller læs om Laravel-udvikling som fundament for disse løsninger.