When to use Cloud Tasks for explicit, rate-limited calls to one endpoint and when to use Pub/Sub for events with several consumers, with the limits that decide it, and how Encore provisions Pub/Sub on GCP from code.

Cloud Tasks and Pub/Sub both move work off the request path on Google Cloud, and both retry with at-least-once delivery, which is why the choice between them often gets made by whichever one the first tutorial used. They are built for different jobs: Cloud Tasks queues a call to one specific endpoint and lets the producer control when and how fast it runs, while Pub/Sub publishes an event that any number of subscribers can react to without the publisher knowing about them.
This comparison covers the limits that decide between them, using Google's own feature table, and how Encore gives you Pub/Sub on GCP from a topic declared in your backend code.
The Cloud Tasks and Pub/Sub columns follow Google's Choosing Pub/Sub or Cloud Tasks page.
Encore combines an open source infrastructure SDK with a platform that provisions and deploys into your own AWS or GCP account. Topics and subscriptions are declared in TypeScript or Go next to the code that publishes and consumes them, and on GCP Encore creates the Pub/Sub topic and subscriptions in your project and gives each service a service account that can only publish or consume what it uses, as the GCP infrastructure docs describe.
Run in your terminal to get started locally.
// orders/orders.ts
import { api } from "encore.dev/api";
import { Topic } from "encore.dev/pubsub";
export interface OrderPlaced {
orderId: string;
email: string;
totalCents: number;
}
// A Pub/Sub topic on GCP, SNS and SQS on AWS.
export const orderPlaced = new Topic<OrderPlaced>("order-placed", {
deliveryGuarantee: "at-least-once",
});
export const place = api(
{ expose: true, method: "POST", path: "/orders" },
async (o: OrderPlaced): Promise<{ messageId: string }> => {
const messageId = await orderPlaced.publish(o);
return { messageId };
},
);
The receipts service consumes the topic. maxConcurrency caps how many messages it handles at once, which covers the common Cloud Tasks reason of not overwhelming a downstream email API, and the retry policy backs off before a message lands in the dead-letter queue:
// receipts/receipts.ts
import { Subscription } from "encore.dev/pubsub";
import { orderPlaced } from "../orders/orders";
const _ = new Subscription(orderPlaced, "send-receipt", {
maxConcurrency: 10,
ackDeadline: "60s",
retryPolicy: { minBackoff: "10s", maxBackoff: "10m", maxRetries: 8 },
handler: async (o) => {
// Delivery is at least once: key the send on o.orderId so a retry
// does not email the customer twice.
console.log("sending receipt", o.orderId);
},
});
A second consumer, such as an analytics service that records the order, is another Subscription in its own service, and it gets its own copy of every event with no change to the publisher, which is the part Cloud Tasks cannot do. encore run runs topics and subscriptions locally, so the publish and both handlers appear in one trace, and the Pub/Sub docs cover ordering keys, attributes and exactly-once topics.
A coding agent adding a consumer works in one file and tests it against the local topic before review, with no IAM binding or push endpoint to set up in the GCP console. The same code deploys to AWS as SNS and SQS if the app ever moves.
Every pull request can get a preview environment with its own topic and subscription, and the local development dashboard shows each published message and the subscriber call in one trace.
Best fit: GCP backends where events flow between your own services and you want Pub/Sub topics, subscriptions, dead-letter handling and service accounts set up in your project from the code.
A Cloud Tasks queue holds tasks that each target one HTTP endpoint, typically a Cloud Run service, with an OIDC token for authentication. The producer decides everything about execution: scheduleTime delays a task by minutes or days, a task name deduplicates creation so a retried request does not enqueue twice, and the queue's maxDispatchesPerSecond and maxConcurrentDispatches keep a fragile downstream API from being flooded. HTTP targets can run for up to 30 minutes per attempt.
The limits are Google's: 500 dispatches per second per queue, a 1 MB task size and one handler per task. Fanning one event out to three services means creating three tasks, and the producer has to know every consumer's URL. Each queue and its IAM (the service account that creates tasks, roles/run.invoker for the token's identity) is configured outside the application, and there is no official local emulator, so tests usually call the handler directly.
Best fit: Delayed or rate-limited calls to one endpoint, such as reminder emails due in an hour or syncs against an API that allows ten requests per second.
Pub/Sub decouples publishers from subscribers: each subscription gets every message, push subscriptions call an HTTPS endpoint and pull subscriptions let workers fetch at their own pace. It accepts messages up to 10 MB, has no fixed per-topic delivery rate beyond regional quotas, supports ordering keys and dead-letter topics, and retains messages for up to 31 days. Google ships an official emulator for local development.
What it does not do is control execution: there is no scheduled delivery and no deduplication at publish time, so delays live in your code and handlers must be idempotent. Push subscriptions also need an endpoint, an authenticated service account and a dead-letter topic with its own IAM grants, which is the setup Encore handles from the subscription declaration.
Best fit: Events with several consumers, high-throughput ingestion, and any flow where the publisher should not know who reacts.
Pick Encore when the work is events between your own services on GCP and you want Pub/Sub topics and subscriptions provisioned from code, with concurrency limits and retries declared next to the handler and a local loop that runs everything. Pick Cloud Tasks when one endpoint must be called at a specific time or a strict rate. Pick Pub/Sub directly when a platform team owns topics and subscriptions and other systems publish to them.
Cloud Tasks is explicit invocation: the producer creates a task aimed at one HTTP endpoint and controls when and how fast it runs, with scheduling, deduplication by task name and per-queue rate limits. Pub/Sub is implicit invocation: the publisher sends an event to a topic without knowing who consumes it, and every subscription gets its own copy.
Use Cloud Tasks when one specific endpoint must be called later or at a controlled rate, such as calling a rate-limited third-party API, delaying a reminder by an hour, or deduplicating task creation. Use Pub/Sub when an event should reach several independent consumers or when volume can exceed 500 dispatches per second per queue.
Declare a topic with new Topic<OrderPlaced>("order-placed", { deliveryGuarantee: "at-least-once" }), publish with orderPlaced.publish(event), and add consumers with new Subscription(orderPlaced, "send-receipt", { handler, maxConcurrency: 10 }). On GCP, Encore creates the Pub/Sub topic and subscriptions in your own project and grants each service only the access it needs.
Pub/Sub has no per-queue dispatch rate, but pull subscribers can use flow control and push subscriptions slow down when the endpoint returns errors. Limiting concurrency in the subscriber caps how many messages are processed at once.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.