Resend Supabase Edge function: error

After many hours of trial and error I managed to create a Resend Supabase Edge function.

However, it doesn’t work, I always get an error:

message: “Failed to send a request to the Edge Function”
cause
name: “FunctionsFetchError”

Chrome Network log:

Note: paipa.pm is verified in Resend and I could send a test mail from Resend.

Note: The WeWeb doc is incomplete and not clear enough: Some sentences are cut off, it’s not clear how to set to, from, subject, html dynamically. Use header fields? html = WeWeb Body field?

Email is one of the most important features for an app. IMHO WeWeb should develop a Resend plugin which doesn’t need Supabase Edge functions.

There is a similar issue but the answers do not help:

Or is it a CORS error? I tried this solution: CORS (Cross-Origin Resource Sharing) support for Invoking from the browser | Supabase Docs
but it suddenly displays two errors:
image

In Postman, I get this response:
“message”: “Hello Functions!”

Note that the sent fields are correct:
From:
pm@paipa.pm
Subject:
Test-Mail
To:
email@xxx.com
Request payload:
This is a Testmail from WeWeb with Resend

You need to send CORS headers with your endpoint when there is an OPTIONS request.

I know (see my contribution). Maybe I made an error in the index.ts. I just pasted the code from Supabase at the beginning of the WeWeb index.ts code (and changed the path to cors.ts as well as the API key).
Here’s the complete index.ts, maybe you see the error. VS only says that name Deno is unknown.

// Set the API key for the Resend service
const RESEND_API_KEY = ‘xxxxx’
import { corsHeaders } from ‘d:/_shared/cors.ts’

console.log(Function "browser-with-cors" up and running!)

Deno.serve(async (req) => {
// This is needed if you’re planning to invoke your function from a browser.
if (req.method === ‘OPTIONS’) {
return new Response(‘ok’, { headers: corsHeaders })
}

try {
const { name } = await req.json()
const data = {
message: Hello ${name}!,
}

return new Response(JSON.stringify(data), {
  headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  status: 200,
})

} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { …corsHeaders, ‘Content-Type’: ‘application/json’ },
status: 400,
})
}
})

// Define an asynchronous function (handler) to handle requests with parameters
const handler = async (_request: Request, from: string, to: string, subject: string, html: string): Promise => {
// Send a POST request to the Resend API to send an email
const res = await fetch(‘https://api.resend.com/emails’, {
method: ‘POST’,
headers: {
‘Content-Type’: ‘application/json’, // Set the content type to JSON
Authorization: Bearer ${RESEND_API_KEY}, // Include the API key in the Authorization header
},
body: JSON.stringify({
from: from,
to: to,
subject: subject,
html: html,
}),
})

// Parse the response as JSON
const data = await res.json()

// Return a new Response object with the JSON data, status 200, and appropriate headers
return new Response(JSON.stringify(data), {
status: 200,
headers: {
‘Content-Type’: ‘application/json’,
},
})
}

// Start a Deno HTTP server and pass the handler function with dynamic “from”, “to”, “subject”, and “html” values
Deno.serve(async (req) => {
const { searchParams } = new URL(req.url)

// Example: Extract “from” and “to” values from query parameters
const from = searchParams.get(‘from’) || ‘defaultfrom@example.com’
const to = searchParams.get(‘to’) || ‘defaultto@example.com’
const subject = searchParams.get(‘subject’) || ‘Best email ever :grinning:
const html = searchParams.get(‘html’) || ‘It works!’

// Call the modified handler function with dynamic “from”,“to”, “subject”, and “html” values
return await handler(req, from, to, subject, html)
})

If you want, this is my code for Resend

import "https://esm.sh/@supabase/functions-js/src/edge-runtime.d.ts";
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { corsHeaders } from '../_shared/cors.ts';

// Get the Resend API key from environment variables
const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY');

// Define the handler function for the server
const handler = async (request: Request): Promise<Response> => {
    // Handle CORS preflight request
    if (request.method === 'OPTIONS') {
        return new Response('ok', { headers: corsHeaders });
    }

    // Only allow POST requests
    if (request.method !== 'POST') {
        return new Response('Method not allowed', {
            status: 405,
            headers: corsHeaders,
        });
    }

    let requestBody;
    try {
        // Parse the request body as JSON
        requestBody = await request.json();
    } catch (error) {
        return new Response('Invalid JSON', {
            status: 400,
            headers: corsHeaders,
        });
    }

    // Extract necessary fields from the request body
    const { from, to, subject, html } = requestBody;

    // Check if all required fields are present
    if (!from || !to || !subject || !html) {
        return new Response('Missing required fields', {
            status: 400,
            headers: corsHeaders,
        });
    }

    try {
        // Make a POST request to the Resend API
        const res = await fetch('https://api.resend.com/emails', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${RESEND_API_KEY}`,
            },
            body: JSON.stringify({
                from,
                to,
                subject,
                html,
            }),
        });

        // Check if the request was successful
        if (res.ok) {
            const data = await res.json();
            return new Response(JSON.stringify(data), {
                status: 200,
                headers: { ...corsHeaders, 'Content-Type': 'application/json' },
            });
        } else {
            const errorData = await res.text();
            return new Response(errorData, {
                status: res.status,
                headers: { ...corsHeaders, 'Content-Type': 'application/json' },
            });
        }
    } catch (error) {
        return new Response(JSON.stringify({ error: error.message }), {
            status: 500,
            headers: { ...corsHeaders, 'Content-Type': 'application/json' },
        });
    }
};

// Start the server with the handler
serve(handler);

Provide all these fields in the Body option as arguments.

{
   "from":"hello@broberto.sk", 
   "to":"delivered@resend.dev", 
   "subject": "Broberto is testing", 
   "html":"<h1>Broberto is testing this</h1><p>And it indeed works bro! Lesgoooo!</p>"}

Thank you for sharing your code! However, even after changing this line:
const RESEND_API_KEY = Deno.env.get(‘RESEND_API_KEY’);
to my own API key:
const RESEND_API_KEY = “xxxxx”;
I get the CORS error.
There seems to be an error in this line:
import { serve } from “/http/server.ts | std@0.190.0 | Deno”;
Cannot find module ‘/http/server.ts | std@0.190.0 | Deno’ or its corresponding type declarations.
And name Deno is unkown again if I use your API Key instead of mine.

I looked at the Deno page deno.land/std@0.224.0/http/server.ts and it says to use this string:
import * as mod from “/http/server.ts | std@0.224.0 | Deno”;

But using this doesn’t help.
In the meantime, I found out how to resolve the VS errors: Install the Deno language server and CLI. But this doesn’t resolve the CORS issue. Any further idea?

Additional question:
I would like to use the WeWeb header fields for from, to, and subject instead of putting them into the body:
// Extract necessary fields from the request body
const { from, to, subject, html } = requestBody;

I’m not an experienced coder. How should I change the script to this end (maybe I could fix this by using parts of the WeWeb script)?

I’d probably need to see this to debug this. I do sessions to solve things like this if you’d be interested.

I’d need to see how your function and folders are structured, also see what cors.ts you have. It’s really difficult to tell right away from error logs.

I just booked a session with you tomorrow 16:00 CET. Maybe we can also look into 1 or 2 other issues if time left.
BTW: I use the cors.ts from CORS (Cross-Origin Resource Sharing) support for Invoking from the browser | Supabase Docs

1 Like

Thanks! I will be there :slight_smile: And absolutely, usually once the main challenge is solved I’m open to any other issues/questions etc. so definitely, if you have something else, feel free to prepare it.