Seth Carney
November 17, 2020

Table of contents
When we started building Courier, we investigated GraphQL, but the options for running a serverless version of Apollo (the technology we wanted to use) were limited and less stable. Since we don’t run EC2 or have dedicated servers, that was a major consideration for us. However, that’s changed quite substantially since we first looked at Apollo. Since then, we’ve been able to start transitioning both our internal and external APIs to GraphQL. I’ll explain some of the reasoning behind this below.
REST has been around for a long time, and today it’s still the most standard, widely-accepted way to write an API. REST is a specification that sits on top of HTTP. API calls are structured around objects (like profiles, preferences, and templates) using dedicated HTTP endpoints. For example, if you wanted to expose a way to programmatically manipulate your user profiles, you might have a REST endpoint user/{userId} which can be queried to perform CRUD operations using HTTP GET, POST, DELETE, etc. Writing a REST API is pretty straightforward — but REST can be tricky to use as an API consumer.
First, REST wasn’t designed for complex sequences of operations that don’t fit neatly into the CRUD bucket. It’s not easy to update two objects at the same time, for example, and even retrieving data in certain scenarios can require multiple calls and branching logic, as one endpoint might have to call another one. One of the other downsides of a REST is that it puts a lot of responsibility on the API consumer (which may be your internal developers or your external clients) to know how the underlying data is structured. That’s not optimal for several reasons.
The API calls aren’t oriented to the common actions that the user wants to take, they’re structured rigidly around your objects. That means that someone might have to call the same REST endpoint to set a label and to add a collaborator, even though these are two completely different use cases. Another reason it’s not a good idea to structure your API around how your data is organized is because things change. Changes to your data are inevitable, and it’s hard to adapt REST APIs to these changes (although in case you find yourself in this situation, you might want to check out our techniques for standardizing your REST APIs).
GraphQL is a query language with a very developer-friendly approach to building APIs. It is based on the idea that the API consumer shouldn’t have to know anything about how the data is stored internally. Instead, you describe your data’s relational schema and the consumer can query that nested data from a single endpoint that never changes. GraphQL also conforms to the idea of CQRS, or command-query responsibility separation, which means the way you query data is different from the way you mutate data.
One of the things I like best about GraphQL is that as a side-effect of implementing it, you’re forced to live by some of those rules of software engineering that you really should be living by. You have to think about your data holistically, and you don’t end up with a bunch of poorly-designed endpoints lying around as the result of shortcuts you took to meet deadlines.
Because of how it’s built, GraphQL is really good at versioning: you can mark functionality deprecated, and you can change the underlying infrastructure without breaking existing integrations (and without the consumer even knowing). GraphQL also has a solid caching layer, which reduces our total operational costs because we end up not hitting our database as much. Because we’re a serverless shop, we will actually be implementing our caching layer through ElastiCache.
As I mentioned earlier, we’ve done research on the options for implementing GraphQL and kept an eye on the possible solutions for some time. The two best options that emerged for our use case were AppSync from AWS and Apollo GraphQL. We evaluated AppSync because we’re an AWS customer, we use cloud formations, and it was appealing to be able to stand something up quickly. But there were some core security choices we made when implementing multi-tenancy in Cognito that made the switch to AppSync more difficult. We realized that AppSync wasn’t really going to work for us unless we changed some of those fundamental decisions.
But that wasn’t the only reason we decided to go with Apollo. Compared to AppSync, which uses the Apache Velocity templating language (VTL), Apollo is just JavaScript. When we work with it we don’t have to do a lot of the mental context-switching that happens when you use different languages. Not to mention, Apollo is popular for a reason, it’s a rock-solid product that’s constantly evolving and has a growing and supportive community of users. Another reason we chose Apollo is for Apollo Federation, which will help us grow our Graph without affecting our performance as our product scales.
Right now, we’ve moved some of our internal APIs to GraphQL, such as the infrastructure for accessing users and tenants. We’re also building all new features with GraphQL as well.
While it will be some time before we move all of our internal APIs to GraphQL, we have plenty of important candidates for this transition. One use case that illustrates this well is autosave during template creation. When you are editing a template in Studio, you can add blocks, add or remove channels, or add conditions (just to name a few examples) and as soon as you make a change it gets autosaved. Behind the scenes, these edits are funneled through a common processor.
One of the problems in REST is that it’s difficult to do partial updates. The various components end up having to send the whole template resource when they want to update a single field. Yes, you can implement PATCH endpoints, but those also come with their own complications. When you factor in doing validation on the full object with every call, autosave has the potential to become an expensive operation. Moving autosave operations to GraphQL mutations will help us solve this problem outside the constraints of a traditional REST API design and more closely represent the types of actions our users are taking.
As we move all of our internal infrastructure to GraphQL, our ultimate goal is to expose a GraphQL interface to our customers, along with an explorer that will make it so consumers can interact with our schema right from the browser.

How Top Notification Platforms Handle Quiet Hours & Delivery Windows in 2026
No platform offers per-template delivery windows in 2026—it's either per-workflow (Customer.io, Knock), per-campaign (Braze), or global settings. This comparison shows exactly how six platforms handle quiet hours and send time controls based on their documentation and API specs. Braze leads on AI timing (23% open rate lift from Intelligent Timing across their customer base). Novu is the only platform letting subscribers set their own delivery windows. Customer.io and Knock require manual workflow configuration. OneSignal's strength is push-specific optimization across 300K+ apps. Courier combines per-node flexibility with API control. Includes feature matrix, timezone handling, and frequency capping differences.
By Kyle Seyler
January 16, 2026

Notification Observability: How to Monitor Delivery, Engagement, and Provider Health
Notification observability is the practice of monitoring notification delivery, engagement, and provider health using the same tools and discipline you apply to the rest of your application infrastructure. It means tracking whether messages are delivered, opened, and acted on across email, SMS, push, and in-app channels, then surfacing that data in dashboards alongside your other application metrics. Key metrics include delivery rate by channel, bounce and failure rates, provider latency, open rate trends, and click-through rates by template. Teams can build notification observability through DIY webhook handlers that pipe provider events to Datadog or Prometheus, log aggregation from application send logs, or notification platforms with built-in observability integrations. This matters most for multi-channel systems, business-critical notifications like password resets and payment confirmations, and teams using multiple providers with fallback routing.
By Kyle Seyler
January 15, 2026

SMS Opt-Out Rules in 2026
TCPA consent rules changed in April 2025. Consumers can now revoke consent using any reasonable method, including keywords like "stop," "quit," "end," "revoke," "opt out," "cancel," or "unsubscribe." Businesses must honor opt-out requests within 10 business days, down from 30. The controversial "revoke all" provision, which would require opt-outs to apply across all automated messaging channels, has been delayed until January 2027 and may be eliminated entirely. SMS providers like Twilio handle delivery infrastructure and STOP keyword responses at the number level. They don't sync opt-outs to your email provider, push notification service, or in-app messaging. That cross-channel gap is your responsibility. Courier provides unified preference management that enforces user choices across SMS, email, push, and chat automatically.
By Kyle Seyler
January 13, 2026
© 2026 Courier. All rights reserved.