#how to run an internal action when `ctx` is not available to us

17 messages · Page 1 of 1 (latest)

inland pike
#

how to run an internal action when ctx is not available to us

export const NodemailerOTP = Email({
  server: {
    host: process.env.EMAIL_SERVER_HOST,
    port: Number(process.env.EMAIL_SERVER_PORT),
    auth: {
      user: process.env.EMAIL_SERVER_USER,
      pass: process.env.EMAIL_SERVER_PASSWORD,
    },
  },
  from: process.env.EMAIL_FROM,
  id: "nodemailer-otp",
  maxAge: 60 * 15, // 15 minutes
  generateVerificationToken: generateOTP,
  sendVerificationRequest: async ({
    identifier: toEmail,
    provider,
    token,
  },) => {
    // how to run an internal action? there is no `ctx`!!
  },
});

I am using the above in convexAuth

export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
  providers: [NodemailerOTP],
  callbacks: {
  
  },
});
grim lilyBOT
#

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

#

When implementing a Convex Auth provider (like your NodemailerOTP example), the sendVerificationRequest function does not receive a Convex ctx, so you cannot directly call internal actions, queries, or mutations from within it.

There is no supported way to run an internal action from sendVerificationRequest when ctx is not available. This is a limitation of the provider API: it is not running inside a Convex function context, but rather as part of the authentication provider's own logic. This is also why you cannot use ctx.runAction, ctx.runMutation, or ctx.runQuery here.

This is confirmed by the Convex Auth documentation and community discussions—when writing custom providers, you are expected to perform the necessary side effects (like sending emails) directly in the sendVerificationRequest function, using standard Node.js libraries or SDKs, not by calling Convex internal actions or mutations. For example, the official docs and examples for OTP and password flows show direct use of email SDKs (like Resend or Nodemailer) inside sendVerificationRequest, not Convex actions or mutations see example and another example.

If you need to perform additional logic that would normally live in an internal action, you must implement it directly in the sendVerificationRequest function, or refactor your logic so that it does not require a Convex ctx in this context.

#

Summary:
You cannot run an internal action from sendVerificationRequest because ctx is not available. You must perform your side effects (like sending emails) directly in that function using standard libraries, as shown in the Convex Auth documentation Magic Links and OTPs.

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

#

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

inland pike
#

@grim lily is there any clever work around for this?

grim lilyBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

There is no officially supported or documented workaround to run an internal action from within a Convex Auth provider's sendVerificationRequest when ctx is not available. The Convex team and community discussions confirm that this function is intentionally designed to run outside of a Convex function context, so you cannot access ctx or invoke internal actions, queries, or mutations from here. The recommended approach is to perform any necessary side effects (like sending emails) directly using Node.js libraries within sendVerificationRequest itself, as shown in the Convex Auth documentation and examples see example.

If you need to trigger additional logic that would normally be handled by an internal action, you would need to refactor your flow so that this logic is either handled directly in sendVerificationRequest or moved to a place where you do have access to ctx, such as within a Convex mutation, action, or scheduled job.

In summary, there is no clever workaround documented in the knowledge sources to bypass this limitation. If you have a specific use case, you may want to discuss it with the Convex team for potential feature requests or alternative patterns.

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

#

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

inland pike
#

@grim lily can i use fetch() in sendVerificationRequest and modify my action code to somehow call it

"use node";

import { ConvexError, v } from "convex/values";
import { action } from "gconvex/_generated/server";
import nodemailer from "nodemailer";
import { type NodemailerConfig } from "@auth/core/providers/nodemailer";

export const sendNodemailerOtp = action({
  args: {
    provider: v.any(),
    toEmail: v.string(),
    token: v.string(),
  },
  handler: async (_, { provider, toEmail, token }) => {
    const { server, from } = provider as NodemailerConfig;

    const transporter = nodemailer.createTransport(server);

    const subject = "🔐 Your ScreenTickr One-Time Password (OTP)";
    const text = `Your OTP is: ${token}`;
    const html = `
            <div style="font-family: sans-serif; line-height: 1.5;">
              <h2>🔐 ScreenTickr Login</h2>
              <p>Your one-time password is:</p>
              <p style="font-size: 24px; font-weight: bold; letter-spacing: 4px;">${token}</p>
              <p>This OTP is valid for 15 minutes.</p>
            </div>
          `;

    try {
      const info = await transporter.sendMail({
        to: toEmail,
        from,
        subject,
        text,
        html,
      });

      console.log("✅ OTP email sent:", info.messageId);
    } catch (error) {
      console.error("❌ Failed to send OTP email:", error);
      const msg =
        error instanceof Error ? error.message : JSON.stringify(error);
      throw new ConvexError(`Email OTP delivery failed: ${msg}`);
    }
  },
});
grim lilyBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You can use fetch() inside sendVerificationRequest in your Convex Auth provider, but you cannot directly call a Convex action (like sendNodemailerOtp) from there, because Convex actions require a Convex context (ctx), which is not available in this environment. The Convex Auth provider code (including sendVerificationRequest) runs outside the Convex function runtime, so it cannot invoke internal actions, queries, or mutations directly see docs.

If you want to use your existing action code for sending emails, you would need to expose it as an HTTP endpoint (using a Convex HTTP action), and then call that endpoint using fetch() from within sendVerificationRequest. However, this approach is not documented or recommended in the official Convex documentation, and may introduce security or architectural concerns.

The recommended approach is to perform the email sending logic directly inside sendVerificationRequest using Node.js libraries (like nodemailer), as shown in the Convex Auth documentation and examples. This is how the Convex team demonstrates sending OTPs and magic links, and it avoids the need to call Convex actions from outside the Convex runtime see example.

Summary:

  • You can use fetch() in sendVerificationRequest, but you cannot call Convex actions directly.
  • The recommended pattern is to send emails directly in sendVerificationRequest using Node.js libraries, not by calling Convex actions see docs.
  • There is no officially documented or supported way to call a Convex action from sendVerificationRequest.

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

#

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

inland pike
#

@grim lily okay expose it as an HTTP action.

grim lilyBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Yes, you can expose your email-sending logic as a Convex HTTP action and then call it using fetch() from within your sendVerificationRequest function. Here’s how you can do it based on the Convex documentation:

1. Define an HTTP Action

Create an HTTP action that wraps your email-sending logic. For example, in convex/http.ts:

import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { sendNodemailerOtp } from "./yourActionFile"; // Import your action

const http = httpRouter();

http.route({
  path: "/send-otp",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    const { provider, toEmail, token } = await request.json();
    // Call your action logic directly here
    await ctx.runAction(sendNodemailerOtp, { provider, toEmail, token });
    return new Response(null, { status: 200 });
  }),
});

export default http;

See: Defining HTTP actions

2. Call the HTTP Action from sendVerificationRequest

In your provider, use fetch() to call the HTTP endpoint you just created:

sendVerificationRequest: async ({ identifier: toEmail, provider, token }) => {
  await fetch(`${process.env.CONVEX_DEPLOYMENT_URL}/send-otp`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ provider, toEmail, token }),
  });
}

Make sure CONVEX_DEPLOYMENT_URL is set to your deployment’s .convex.site URL (not .convex.cloudsee: HTTP Actions.

3. Security Considerations

#

Since this endpoint is now public, you should implement authentication or secret validation to ensure only your backend can call it. For example, include a secret in the request body or headers and check it in your HTTP action handler see: Discord support thread.


Summary:
You can expose your action as an HTTP action and call it from sendVerificationRequest using fetch(). This is a documented and supported pattern for integrating external systems with Convex see: HTTP Actions.

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