Add typed error handling, retries, and webhook receiver

- Typed exceptions: LeadMailException base with LeadMailRequestException
  (structured statusCode/errorCode/logId/validationErrors) and
  LeadMailConnectionException; sendEmail/getDomains now throw these instead
  of raw Guzzle exceptions, and a malformed body is no longer a silent null.
- Automatic retry with exponential backoff on idempotent calls
  (getDomains, verifyEmail); sends are never retried to avoid duplicates.
- Webhook receiver: auto-registered route + LeadMailWebhookController that
  verifies the HMAC signature, logs failures, and dispatches a
  LeadMailWebhookReceived event. WebhookSignature/WebhookEvent/LeadMailWebhook
  helpers for manual handling.
- Webhook self-registration client methods (registerWebhook/getWebhook/
  deleteWebhook) and a promptless `leadmail:install` command that registers
  the URL and writes LEADMAIL_WEBHOOK_SECRET to .env.
- Null-safe client binding when LEADMAIL_TOKEN is unset.
- Test suite (Pest + Testbench) covering all of the above.
This commit is contained in:
netlas
2026-06-09 11:14:10 +03:00
parent bf8aac48e6
commit b2b61a26ed
24 changed files with 1467 additions and 21 deletions

View File

@@ -0,0 +1,42 @@
<?php
namespace LeadM\LeadMail\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use LeadM\LeadMail\Events\LeadMailWebhookReceived;
use LeadM\LeadMail\Exceptions\InvalidWebhookSignatureException;
use LeadM\LeadMail\Webhooks\LeadMailWebhook;
/**
* Default receiver for LeadMail webhooks, auto-registered by the service
* provider. Verifies the signature, logs failures, and dispatches a
* LeadMailWebhookReceived event for custom handling.
*/
class LeadMailWebhookController
{
public function __invoke(Request $request, LeadMailWebhook $webhook): Response
{
try {
$event = $webhook->parse($request);
} catch (InvalidWebhookSignatureException) {
Log::warning('LeadMail webhook rejected: invalid or missing signature.');
return response('Invalid signature.', 403);
}
if ($event->isFailure()) {
Log::warning('LeadMail reported a failed email.', [
'log_id' => $event->logId,
'to' => $event->to,
'error_code' => $event->errorCode,
'error_message' => $event->errorMessage,
]);
}
event(new LeadMailWebhookReceived($event));
return response()->noContent();
}
}