Resend Email Service And Webhooks

This project uses Resend for outbound sales-invoice email delivery and for delivery/open/click/failure lifecycle webhooks.

Outbound Email Service

Outbound sends happen in accounting/invoice_email.py through Resend’s /emails API and are called from SalesInvoiceEmailSendView.

Required environment variables:

  • RESEND_API_KEY

  • RESEND_FROM_EMAIL (or legacy alias RESEND_EMAIL_FROM)

Optional environment variables:

  • RESEND_REPLY_TO

  • RESEND_API_BASE_URL (default: https://api.resend.com)

  • RESEND_REQUEST_TIMEOUT_SECONDS (default: 20)

  • RESEND_USER_AGENT (default: inkforje-accounting/1.0)

Webhook Endpoint

The application exposes a CSRF-exempt webhook endpoint:

  • /ledger/webhooks/resend/

Register this URL in Resend as:

  • https://<your-domain>/ledger/webhooks/resend/

Signature Verification

Incoming webhooks are verified with Svix headers:

  • svix-id

  • svix-timestamp

  • svix-signature

The app verifies signatures using RESEND_WEBHOOK_SECRET before processing events.

Webhook Secret Configuration

Set this in your environment file:

  • RESEND_WEBHOOK_SECRET=whsec_xxxxx

Local env file path:

  • /Users/danielmontecillo/Development/django/inkforje_accounting/.env

Production env file path (from deployment workflow):

  • <DEPLOY_PATH>/.env

Event Handling And Status Mapping

Supported Resend event types and mapped SalesInvoiceEmailLog.status:

  • email.scheduled -> scheduled

  • email.sent -> sent

  • email.delivered -> delivered

  • email.delivery_delayed -> delivery_delayed

  • email.opened -> opened

  • email.clicked -> clicked

  • email.complained -> complained

  • email.bounced -> bounced

  • email.suppressed -> suppressed

  • email.failed -> failed

Webhook events that do not match a known provider_message_id are safely acknowledged with HTTP 204 and ignored.

Data Stored Per Event

For each matched event, the app updates the existing email log row with:

  • status

  • last_event_type

  • last_event_at

  • provider_response (raw event payload)

  • error_message (for failed/bounced/suppressed/delayed reasons when present)

UI Surfaces Updated

Webhook-driven status appears in:

  • Sales Invoice detail status indicator

  • Sales Invoice update status indicator

  • Sales Correspondence list

  • Customer detail correspondence table

Setup Checklist

  1. Add Resend API and webhook environment variables.

  2. Run migrations:

    uv run python manage.py migrate
    
  3. Restart the Django process.

  4. In Resend, subscribe email event types listed above.

  5. Send a test sales invoice email and verify status changes in the UI.