Transactional
Your application’s e-mails, finally kept
A receipt that does not arrive is a customer who calls. Lekalao sends your applications’ e-mails through your provider, and keeps every one of them in a log.
Free up to 1,000 subscribers, no card. Paid plans: 14 days to try.
Atelier Mboa
Your receipt no. 20418
Thank you, Mireille.
- Glazed bowl ×2
- 18,000
- Delivery
- 2,500
Total20,500 FCFA
Log
Resend- Accepted by Postmark09:41:07
- Delivered09:41:09
- Opened, twice09:52:30
order-receipt · fr
Three ways to plug in
- The API
- One POST: variables, attachments, the language, the provider.
- The Laravel transport
- MAIL_MAILER=lekalao, and your code does not change.
- The SMTP relay
- For what only speaks SMTP: an API token is the password. The relay itself does not speak TLS: the hosted service reaches it over TLS on port 465, through the proxy in front of it; a self-hosted one puts it behind its own.
curl -X POST "$LEKALAO_URL/api/v1/transactional-mails/send" \
-H "Authorization: Bearer $LEKALAO_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2026-00481-confirmation" \
-d '{
"template": "order-confirmation",
"to": ["ada@example.com"],
"locale": "fr",
"variables": {
"customer": { "name": "Ada" },
"order": { "number": "2026-00481", "total": "12 500 FCFA" }
}
}'// config/mail.php
'mailers' => [
'lekalao' => [
'transport' => 'lekalao-api',
'endpoint' => env('LEKALAO_URL'),
'token' => env('LEKALAO_TOKEN'),
],
],
// .env: MAIL_MAILER=lekalao
Mail::to($order->customer)->send(new OrderConfirmed($order));MAIL_MAILER=smtp
MAIL_HOST=$LEKALAO_SMTP_HOST
MAIL_PORT=2525
MAIL_USERNAME=lekalao
# An API token with the "write" ability
MAIL_PASSWORD=$LEKALAO_TOKENThe log answers “did they get it?”
Recipients, content, delivery, opens, clicks, and a button to send it again. Statistics per template.
Two streams, two providers
Campaigns through one, invoices through the other. A campaign that draws complaints never spoils the stream that sends your receipts.
Templates marketing can proofread
HTML, Markdown or MJML, one version per language, default recipients. The application sends the variables; the wording is corrected without a deployment.
Webhooks that prove themselves
Subscriptions, departures, tags, bounces, complaints: each call is signed with HMAC-SHA256. The log keeps the answer it received, and an address that fails ten times in a row is switched off.
Verify a webhook
Route::post('/webhooks/lekalao', function (Request $request) {
$expected = hash_hmac(
'sha256',
$request->getContent(),
config('services.lekalao.webhook_secret'),
);
$received = (string) $request->header('X-Lekalao-Signature');
abort_unless(hash_equals($expected, $received), 401);
// subscriber.unsubscribed, mail.bounced, campaign.sent…
return response()->noContent();
});Everything the interface does, your code does too
The same key never writes twice: an Idempotency-Key returns the first answer for 24 hours. Limits are counted per token, not per address. A thousand subscribers go in one call, with a report per row.
A test refuses any route without a description, and any description without a route.
No SDK, on purpose
Five packages to maintain while the API still moves would cost more than they bring. The OpenAPI specification is right; your generator does the rest. For Laravel, the transport is ready.
How it is built
Laravel, PostgreSQL, Redis. More than 1,000 automated tests, one of which opens every page with data in it. When accounts are opened by hand, the form at the foot of this site is the product’s own sign-up form.
Start free. Try the rest for 14 days.
Free up to 1,000 subscribers, with no card. Starter, Growth and Pro can be tried for 14 days, with no card either; if you do not pay, your team simply goes back to Free on day 15.