- 
                Notifications
    
You must be signed in to change notification settings  - Fork 9.8k
 
[Queues] Add rate limit tutorial #16797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
    
  
     Merged
                    Changes from 6 commits
      Commits
    
    
            Show all changes
          
          
            9 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      b1c17b6
              
                add rate limit tutorial
              
              
                harshil1712 e123e64
              
                Update src/content/docs/queues/tutorials/handle-rate-limits/index.mdx
              
              
                harshil1712 645183c
              
                [Hyperlint] Update checks to be more forgiving for Wrangler capitaliz…
              
              
                kodster28 903342b
              
                Revert "[Hyperlint] Update checks to be more forgiving for Wrangler c…
              
              
                kodster28 6302bd0
              
                update queues resend tutorial
              
              
                harshil1712 00297ad
              
                update repo link and minor changes
              
              
                harshil1712 7db30e4
              
                minor fixes
              
              
                harshil1712 1ec3fb5
              
                update Render componenet params
              
              
                harshil1712 134d9b1
              
                update the category
              
              
                harshil1712 File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
        
          
  
    
      
          
            395 changes: 395 additions & 0 deletions
          
          395 
        
  src/content/docs/queues/tutorials/handle-rate-limits/index.mdx
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,395 @@ | ||
| --- | ||
| updated: 2024-09-25 | ||
| difficulty: Beginner | ||
| title: Handle rate limits of external APIs | ||
| summary: Example of how to use Queues to handle rate limits of external APIs. | ||
| content_type: 📝 Tutorial | ||
| pcx_content_type: tutorial | ||
| products: | ||
| - Workers | ||
| - Queues | ||
| languages: | ||
| - TypeScript | ||
| sidebar: | ||
| order: 1002 | ||
| head: | ||
| - tag: title | ||
| content: Cloudflare Queues - Queues & Rate Limits | ||
| description: Example of how to use Queues to handle rate limits of external APIs. | ||
| --- | ||
| 
     | 
||
| import { Render, PackageManagers } from "~/components"; | ||
| 
     | 
||
| This tutorial explains how to use Queues to handle rate limits of external APIs. In this tutorial, you will build an application that sends email notifications using [Resend](https://www.resend.com/), but you can use this pattern to handle rate limits of any external API. | ||
| 
     | 
||
| Resend is a service that allows you to send emails from your application. It provides a simple API that you can use to send emails. Resend has a default [rate limit](https://resend.com/docs/api-reference/introduction#rate-limit) of 2 requests per second. You will use Queues to handle the rate limit of Resend. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ## Prerequisites | ||
| 
     | 
||
| <Render file="prereqs" product="workers" /> | ||
| 
     | 
||
| 4. Sign up for Resend and generate an API key by following the instructions on the [Resend documentation](https://resend.com/docs/dashboard/api-keys/introduction). | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ### Queues access | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| Additionally, you will need access to Cloudflare Queues. | ||
| 
     | 
||
| <Render file="enable-queues" /> | ||
| 
     | 
||
| ## 1. Create new Workers application | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| To get started, create a Worker application using the [`create-cloudflare` CLI](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare). Open a terminal window and run the following command: | ||
| 
     | 
||
| <PackageManagers | ||
| type="create" | ||
| pkg="cloudflare@latest" | ||
| args={"resend-rate-limit-queue"} | ||
| /> | ||
| 
     | 
||
| <Render | ||
| file="c3-post-run-steps" | ||
| product="workers" | ||
| params={{ | ||
| one: "Hello World example", | ||
| two: "Hello World Worker", | ||
| three: "TypeScript", | ||
| four: "Yes", | ||
| five: "No", | ||
| }} | ||
| /> | ||
| 
     | 
||
| Then, move into your newly created directory: | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```sh frame="none" | ||
| cd resend-rate-limit-queue | ||
| ``` | ||
| 
     | 
||
| ## 2. Set up a Queue | ||
| 
     | 
||
| You need to create a Queue and a binding to your Worker. Run the following command to create a Queue named `rate-limit-queue`. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```sh title="Create a Queue" | ||
| npx wrangler queues create rate-limit-queue | ||
| ``` | ||
| 
     | 
||
| ```txt title="Output" | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| Creating queue rate-limit-queue. | ||
| Created queue rate-limit-queue. | ||
| ``` | ||
| 
     | 
||
| ### Add Queue bindings to wrangler.toml | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| Next, in your `wrangler.toml` file, add the following: | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```toml | ||
| [[queues.producers]] | ||
| binding = "EMAIL_QUEUE" | ||
| queue = "rate-limit-queue" | ||
| 
     | 
||
| [[queues.consumers]] | ||
| queue = "rate-limit-queue" | ||
| max_batch_size = 2 | ||
| max_batch_timeout = 10 | ||
| max_retries = 3 | ||
| ``` | ||
| 
     | 
||
| Adding the `max_batch_size` of 2 to the consumer queue is important because the Resend API has a default rate limit of 2 requests per second. This batch size allows the queue to process the message in the batch size of 2. If the batch size is less than 2, the queue will wait for 10 seconds to collect the next message. If no more messages are available, the queue will process the message in the batch. For more information, refer to the [Batching, Retries and Delays documentation](/queues/configuration/batching-retries) | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| Your final `wrangler.toml` file should look similar to the one below. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```toml title="wrangler.toml" | ||
| #:schema node_modules/wrangler/config-schema.json | ||
| name = "resend-rate-limit-queue" | ||
| main = "src/index.ts" | ||
| compatibility_date = "2024-09-09" | ||
| compatibility_flags = ["nodejs_compat"] | ||
| 
     | 
||
| [[queues.producers]] | ||
| binding = "EMAIL_QUEUE" | ||
| queue = "rate-limit-queue" | ||
| 
     | 
||
| [[queues.consumers]] | ||
| queue = "rate-limit-queue" | ||
| max_batch_size = 2 | ||
| max_batch_timeout = 10 | ||
| max_retries = 3 | ||
| ``` | ||
| 
     | 
||
| ## 3. Add bindings to environment | ||
| 
     | 
||
| Add the bindings to the environment interface in `worker-configuration.d.ts`, so TypeScript correctly types the bindings. Type the queue as `Queue<any>`. The following step will show you how to change this type. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```ts title="worker-configuration.d.ts" | ||
| interface Env { | ||
| EMAIL_QUEUE: Queue<any>; | ||
| } | ||
| ``` | ||
| 
     | 
||
| ## 4. Send message to the queue | ||
| 
     | 
||
| The application will send a message to the queue when this Worker receives a request. For simplicity, you will send the email address as a message to the queue. A new message will be sent to the queue with a delay of 1 second. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```ts title="src/index.ts" | ||
| export default { | ||
| async fetch(req: Request, env: Env): Promise<Response> { | ||
| try { | ||
| await env.EMAIL_QUEUE.send( | ||
| { email: await req.text() }, | ||
| { delaySeconds: 1 }, | ||
| ); | ||
| return new Response("Success!"); | ||
| } catch (e) { | ||
| return new Response("Error!", { status: 500 }); | ||
| } | ||
| }, | ||
| }; | ||
| ``` | ||
| 
     | 
||
| This will accept requests to any subpath and forwards the request's body. It expects that the request body only contains an email. In production, you should check that the request was a `POST` request. Moreover, you should avoid sending such sensitive information (email) directly to the queue. Instead, you can send a message to the queue that contains a unique identifier for the user. Then, your consumer queue can use the unique identifier to look up the email address in a database and use that to send the email. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ## 5. Process the messages in the queue | ||
| 
     | 
||
| After the message is sent to the queue, it will be processed by the consumer Worker. The consumer Worker will process the message and send the email. | ||
| 
     | 
||
| Since you have not configured Resend yet, you will log the message to the console. After you configure Resend, you will use it to send the email. | ||
| 
     | 
||
| Add the `queue()` handler as shown below. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```ts title="src/index.ts" ins={1-3,17-28} | ||
| interface Message { | ||
| email: string; | ||
| } | ||
| 
     | 
||
| export default { | ||
| async fetch(req: Request, env: Env): Promise<Response> { | ||
| try { | ||
| await env.EMAIL_QUEUE.send( | ||
| { email: await req.text() }, | ||
| { delaySeconds: 1 }, | ||
| ); | ||
| return new Response("Success!"); | ||
| } catch (e) { | ||
| return new Response("Error!", { status: 500 }); | ||
| } | ||
| }, | ||
| async queue(batch: MessageBatch<Message>, env: Env): Promise<void> { | ||
| for (const message of batch.messages) { | ||
| try { | ||
| console.log(message.body.email); | ||
| // After configuring Resend, you can send email | ||
| message.ack(); | ||
| } catch (e) { | ||
| console.error(e); | ||
| message.retry({ delaySeconds: 5 }); | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| ``` | ||
| 
     | 
||
| The above `queue()` handler will log the email address to the console and send the email. It will also retry the message if the email sending fails. The `delaySeconds` is set to 5 seconds to avoid sending the email too quickly. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| To test the application, run the following command. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```sh title="Start the development server" | ||
| npm run dev | ||
| ``` | ||
| 
     | 
||
| Use the following cURL command to send a request to the application. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```sh title="Test with a cURL request" | ||
| curl -X POST -d "[email protected]" http://localhost:8787/ | ||
| ``` | ||
| 
     | 
||
| On success, you should see the following output in the console. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```txt title="Output" | ||
| [wrangler:inf] POST / 200 OK (2ms) | ||
| QueueMessage { | ||
| attempts: 1, | ||
| body: { email: '[email protected]' }, | ||
| timestamp: 2024-09-12T13:48:07.236Z, | ||
| id: '72a25ff18dd441f5acb6086b9ce87c8c' | ||
| } | ||
| ``` | ||
| 
     | 
||
| ## 6. Set up Resend | ||
| 
     | 
||
| To call the Resend API, you need to configure the Resend API key. Create a `.dev.vars` file in the root of your project and add the following: | ||
| 
     | 
||
| ```txt title=".dev.vars" | ||
| RESEND_API_KEY='your-resend-api-key' | ||
| ``` | ||
| 
     | 
||
| Replace `your-resend-api-key` with your actual Resend API key. | ||
| 
     | 
||
| Next, update the `Env` interface in `worker-configuration.d.ts` to include the `RESEND_API_KEY` variable. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
                
      
                  kodster28 marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```ts title="worker-configuration.d.ts" ins={3} | ||
| interface Env { | ||
| EMAIL_QUEUE: Queue<any>; | ||
| RESEND_API_KEY: string; | ||
| } | ||
| ``` | ||
| 
     | 
||
| Lastly, install the [`resend` package](https://www.npmjs.com/package/resend) using the following command | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```sh title="Install Resend" | ||
| npm install resend | ||
| ``` | ||
| 
     | 
||
| You can now use the `RESEND_API_KEY` variable in your code. | ||
| 
     | 
||
| ## 7. Send email with Resend | ||
| 
     | 
||
| In your `src/index.ts` file, import the Resend package and update the `queue()` handler to send the email. | ||
| 
     | 
||
| ```ts title="src/index.ts" ins={1,21,26-40} del={24,41} | ||
| import { Resend } from "resend"; | ||
| 
     | 
||
| interface Message { | ||
| email: string; | ||
| } | ||
| 
     | 
||
| export default { | ||
| async fetch(req: Request, env: Env): Promise<Response> { | ||
| try { | ||
| await env.EMAIL_QUEUE.send( | ||
| { email: await req.text() }, | ||
| { delaySeconds: 1 }, | ||
| ); | ||
| return new Response("Success!"); | ||
| } catch (e) { | ||
| return new Response("Error!", { status: 500 }); | ||
| } | ||
| }, | ||
| async queue(batch: MessageBatch<Message>, env: Env): Promise<void> { | ||
| // Initialize Resend | ||
| const resend = new Resend(env.RESEND_API_KEY); | ||
| for (const message of batch.messages) { | ||
| try { | ||
| console.log(message.body.email); | ||
| // send email | ||
| const sendEmail = await resend.emails.send({ | ||
| from: "[email protected]", | ||
| to: [message.body.email], | ||
| subject: "Hello World", | ||
| html: "<strong>Sending an email from Worker!</strong>", | ||
| }); | ||
| 
     | 
||
| // check if the email failed | ||
| if (sendEmail.error) { | ||
| console.error(sendEmail.error); | ||
| message.retry({ delaySeconds: 5 }); | ||
| } else { | ||
| // if success, ack the message | ||
| message.ack(); | ||
| } | ||
| message.ack(); | ||
| } catch (e) { | ||
| console.error(e); | ||
| message.retry({ delaySeconds: 5 }); | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| ``` | ||
| 
     | 
||
| The `queue()` handler will now send the email using the Resend API. It also checks if the email sending failed and retries the message if it did. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| The final script is included below. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```ts title="src/index.ts" | ||
| import { Resend } from "resend"; | ||
| 
     | 
||
| interface Message { | ||
| email: string; | ||
| } | ||
| 
     | 
||
| export default { | ||
| async fetch(req: Request, env: Env): Promise<Response> { | ||
| try { | ||
| await env.EMAIL_QUEUE.send( | ||
| { email: await req.text() }, | ||
| { delaySeconds: 1 }, | ||
| ); | ||
| return new Response("Success!"); | ||
| } catch (e) { | ||
| return new Response("Error!", { status: 500 }); | ||
| } | ||
| }, | ||
| async queue(batch: MessageBatch<Message>, env: Env): Promise<void> { | ||
| // Initialize Resend | ||
| const resend = new Resend(env.RESEND_API_KEY); | ||
| for (const message of batch.messages) { | ||
| try { | ||
| // send email | ||
| const sendEmail = await resend.emails.send({ | ||
| from: "[email protected]", | ||
| to: [message.body.email], | ||
| subject: "Hello World", | ||
| html: "<strong>Sending an email from Worker!</strong>", | ||
| }); | ||
| 
     | 
||
| // check if the email failed | ||
| if (sendEmail.error) { | ||
| console.error(sendEmail.error); | ||
| message.retry({ delaySeconds: 5 }); | ||
| } else { | ||
| // if success, ack the message | ||
| message.ack(); | ||
| } | ||
| } catch (e) { | ||
| console.error(e); | ||
| message.retry({ delaySeconds: 5 }); | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| ``` | ||
| 
     | 
||
| To test the application, start the development server using the following command. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```sh title="Start the development server" | ||
| npm run dev | ||
| ``` | ||
| 
     | 
||
| Use the following cURL command to send a request to the application. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```sh title="Test with a cURL request" | ||
| curl -X POST -d "[email protected]" http://localhost:8787/ | ||
| ``` | ||
| 
     | 
||
| On the Resend dashboard, you should see that the email was sent to the provided email address. | ||
| 
     | 
||
| ## 8. Deploy your Worker | ||
| 
     | 
||
| To deploy your Worker, run the following command: | ||
| 
     | 
||
| ```sh title="Deploy your Worker" | ||
| npx wrangler deploy | ||
| ``` | ||
| 
     | 
||
| Lastly, add the Resend API key using the following command. | ||
                
      
                  harshil1712 marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| ```sh title="Add the Resend API key" | ||
| npx wrangler secret put RESEND_API_KEY | ||
| ``` | ||
| 
     | 
||
| Enter the value of your API key. Your API key will get added to your project. You can now use the `RESEND_API_KEY` variable in your code. | ||
| 
     | 
||
| You have successfully created a Worker which can send emails using the Resend API respecting rate limits. | ||
| 
     | 
||
| To test your Worker, you could use the following cURL request. Replace `<YOUR_WORKER_URL>` with the URL of your deployed Worker. | ||
| 
     | 
||
| ```bash title="Test with a cURL request" | ||
| curl -X POST -d "[email protected]" <YOUR_WORKER_URL> | ||
| ``` | ||
| 
     | 
||
| Refer to the [GitHub repository](https://github.com/harshil1712/queues-rate-limit) for the complete code for this tutorial. If you are using [Hono](https://hono.dev/), you can refer to the [Hono example](https://github.com/harshil1712/resend-rate-limit-demo). | ||
| 
     | 
||
| ## Related resources | ||
| 
     | 
||
| - [How Queues works](/queues/reference/how-queues-works/) | ||
| - [Queues Batching and Retries](/queues/configuration/batching-retries/) | ||
| - [Resend](https://resend.com/docs/) | ||
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.