#Resend webhooks

7 messages · Page 1 of 1 (latest)

slim fractal
#

I'm using hono to process http requests - as per your blog post, but updated for the latest version of Convex.

I've added an endpoint for processing webhook events from resend:

app.post(
    "/webhooks/resend/:accountId",
    async (c) => {
        console.log("Received resend webhook");

        const accountId = c.req.param("accountId");
        const account = await c.env.runQuery(internal.user.account.fetchAccountInternal, { accountId: accountId as Id<"account"> });
        if (!account) {
            return c.json({ error: "Account not found" }, 404);
        }

        const resend = new Resend(components.resend, {
            apiKey: account.resendApiKey,
            webhookSecret: account.resendWebhookSecret,
            onEmailEvent: internal.email.email.handleEmailEvent,
        })

        const result = await resend.handleResendEventWebhook(c.env, c.req.raw);
        console.log("Resend webhook result: ", result);
        return result;
    }
)

The logged output is:

7/25/2025, 11:59:58 AM [CONVEX H(POST /webhooks/resend/3zweykav7h0650e048nscwhe9pt53m0)] [LOG] 'Received resend webhook'
7/25/2025, 11:59:58 AM [CONVEX H(POST /webhooks/resend/3zweykav7h0650e048nscwhe9pt53m0)] [LOG] 'Resend webhook result: ' Response { bodyUsed: false, headers: Headers {}, ok: true, status: 201, statusText: '', url: '' }

But my handleEmailEvent method is never called. It's just copied from the convex/resend documentation:

  export const handleEmailEvent = internalMutation({
    args: {
      id: vEmailId,
      event: vEmailEvent,
    },
    handler: async (ctx, args) => {
      console.log("Got called back!", args.id, args.event);
    },
  });

Other than using Hono the main difference in how I'm doing things is constructing the Resend instance on the fly, but this is working fine for sending emails, so I assume this is OK?

Any help would be greatly appreciated. Thanks!

languid tangleBOT
#

Hi @slim fractal. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Your approach to handling Resend webhooks with Hono and Convex is very close to the documented pattern, and your code for sending emails and setting up the webhook endpoint aligns with the official Resend Convex Component documentation.

Key points from the docs:

  • The webhook handler should call resend.handleResendEventWebhook(ctx, req) (or c.env, c.req.raw in your Hono context), which is correct in your code.
  • The onEmailEvent callback should be triggered when a valid event is received and processed by the webhook handler.
  • The docs show the handler as an internal mutation, just as you have it.

Potential Issues and Considerations:

  1. Webhook Secret and Event Validation:
    Make sure the webhookSecret you pass to the Resend instance matches exactly what is set in the Resend dashboard for the webhook. If the secret is incorrect, the webhook handler may silently ignore the event or return early, and the callback will not be triggered.

  2. Correct Event Types:
    Ensure that the webhook in the Resend dashboard is configured to send all email.* events, as other event types are ignored by the component (Resend Convex Component – Advanced Usage).

  3. Dynamic Construction of Resend Instance:
    The documentation does not explicitly state whether constructing the Resend instance on the fly per request is supported or recommended, but it does show the instance being created once and reused. While your approach works for sending emails, it's possible that the event handler registration (onEmailEvent) may not be wired up as expected if the instance is created per request. The docs show the Resend instance being created at module scope, not inside the handler.

#

"Update your sendEmails.ts to look something like this:
export const resend: Resend = new Resend(components.resend, { onEmailEvent: internal.example.handleEmailEvent });"
(Resend Convex Component – Advanced Usage)

  1. Handler Registration:
    If you create the Resend instance inside the handler, the onEmailEvent callback may not be registered in time for the webhook event to trigger it, especially if the event is processed asynchronously or in a different context.

Suggested Next Steps:

  • Move the Resend Instance:
    Try moving the Resend instance creation outside the route handler, at module scope, and ensure it is initialized with the correct onEmailEvent handler. If you need to support multiple accounts, you may need to manage a map of Resend instances keyed by account, or refactor so that the event handler is always registered before any webhook is processed.

  • Check Webhook Secret and Event Types:
    Double-check that the webhook secret and event types are set correctly in both Convex and the Resend dashboard.

  • Logging in the Event Handler:
    Add additional logging in your handleEmailEvent mutation to ensure it's not being called but failing silently.

Summary:
The most likely issue is the dynamic construction of the Resend instance inside the handler, which may prevent the onEmailEvent callback from being registered in time. The documentation shows the instance being created at module scope, which ensures the callback is always available when webhook events are processed. Try refactoring to match this pattern and see if your handler is called as expected.

#

If after these changes the issue persists, there may be a subtlety in how the Convex Resend component wires up event handlers that is not fully documented. In that case, reaching out on the Convex Discord or checking for updates to the component may help.

For reference, see the official docs: Resend Convex Component – Advanced Usage.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@slim fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

slim fractal
#

I just tried creating the resend instance outside of the method, at the top of the http.ts file, and I get the exact same output.