Riley Napier
August 18, 2020

There have been many times in my career of building web apps and frontend experiences that I have needed to embed a widget in a DOM that I did not build or have access to. For example, embedding e-commerce widgets in a CMS or building chrome extensions to augment a given website. One of the biggest frustrations I've had is the style collision. In my development environment, all the widgets look great but the second I add the widget to a customer’s page… everything is broken!
The difficulty with embedding widgets in a DOM that you don't own, is that every DOM is going to reference different default fonts and colors. It’s rather normal for a css stylesheet to look like:
Copied!
body {font-family: my-awesome-font;font-size: 16px;color: #eee;line-height: 16px;box-sizing: border-box;}
When I embed my widget into the body of this customer’s page, it will inherit the above styles. While sometimes this is ok, many times it will break the beautiful widget I designed because I designed the widget with a different font-size or padding.
Historically we have had 2 solutions to this problem.
While both solutions can work, they both have rather frustrating aspects you will have to deal with. Below we will go over a few examples of what I’ve done in the past and then talk about what this blog post is all about, the fancy new futuristic way to make composite user interfaces! The Shadow DOM!! <insert sinister laugh here>
With an IFrame, I have no control over the element’s size, so the consumer of my widget will have to accurately carve out space in their dom for my iFrame. If my widget is dynamic in size, this is going to cause all sorts of problems with scrolling and positioning.
The second issue we find with iFrames, is the communication between the iFrame and the parent. While I can now use CustomEvents, I will need to build out an event system for both the parent and the iFrame context. This can be rather frustrating if the client already has a built in SDK. It’s basically building a mini SDK for the SDK for IFrame communication.
And Finally, maybe the most simplistic issue, my consumer can’t tweak ANY of the styles in my iFrame. This can lead to inconsistent user interfaces and just a bad experience all around.

While iFrames will work, they are outdated, difficult to communicate with, and if your widget is dynamic in size, or you need any kind of customization, good luck.
The more common approach I have taken is to just be super specific with my CSS. So namespace everything!!! This can be tedious and will most likely need to be tweaked for each new client integrating your components. The QA process for pushing out an update to the widget is going to be difficult, too! There are so many ways clients can use CSS and have it break your integration.

Ok, so if I don’t want to use an iFrame or be crazy explicit about my CSS, what can I do?
Wow, that sounds spooky… What is the Shadow DOM you ask? It’s an API for DOM encapsulation and we all know how important encapsulation is.
Shadow DOM allows hidden DOM trees to be attached to elements in the regular DOM tree — this shadow DOM tree starts with a shadow root, underneath which can be attached to any elements you want, in the same way as the normal DOM. -Using Shadow DOM
The most basic approach to creating a shadow is to attach it to any dom element:
Copied!
const shadow = element.attachShadow({mode: 'open' || ‘closed’});
The mode open or closed allows you to specify whether or not the page’s JavaScript can interact with the Shadow DOM. Open meaning yes, it can interact and closed, it cannot.
After I’ve created my shadow element, I can append to it just like any normal DOM node.
Copied!
const shadow = element.attachShadow({mode: 'open' || ‘closed’});const styleNode = document.createElement(“style”);style.textContent = `background: blue;font-size: 18px;`;shadow.appendChild(styleNode);const contentNode = document.createElement(“div”);contentNode.textContent = `Hello World`;shadow.appendChild(contentNode);
The above code will create a shadow node, append a node style to the Shadow DOM, and append a div saying Hello World. The style will now be isolated, only affecting the shadow tree and not contaminating the parent! Success!

However, the above example is very verbose and simple and is showing us the bare metal implementation of the Shadow DOM. It’s only scratching the surface of what the Shadow DOM can do. It’s not all that complicated and it’s pretty well supported right now!

I’d like to take a quick moment to highlight a really useful package that I’ve used in the past and really gave me the feeling of “WOW, I might actually be able to use this in production”.
React Shadow makes working with the shadow dom with React easy as pie! The example I used above with react-shadow will look like this
Copied!
import root from 'react-shadow';export default () => (<root.div><div>Hello World</div><style type="text/css">background: blue;font-size: 18px;</style></root.div>);
Now if that isn’t magic, I don’t know what else is! So take a step with me into the future, let’s not be afraid of our Shadow DOM, and let’s make beautiful composite user experiences together!

Check out how we used our Shadow DOM to make a beautiful composite user experience. Sign up for Courier to explore our UI.

Expo Push Notifications: The Complete Implementation Guide (SDK 52+)
Expo push notifications are alerts sent from a server to a user's phone, even when the app isn't open. To set them up, install the expo-notifications library, ask the user for permission, and get a unique push token for their device. Your server sends a message to Expo's push service with that token, and Expo delivers it through Apple or Google. Push notifications only work on real phones, not simulators. Local notifications are different — they're scheduled by the app itself for things like reminders. You can also route Expo push through services like Courier to add email, SMS, and Slack fallbacks.
By Kyle Seyler
February 24, 2026

Best Email API Providers for Developers in 2026: SendGrid vs Postmark vs Mailgun vs SES vs Resend
Your email provider sticks with you longer than most technical decisions. Courier handles notification infrastructure for thousands of teams, so we went deep on the six email providers that show up most: SendGrid, Postmark, Mailgun, Amazon SES, Resend, and SMTP. This guide covers real API primitives, actual code from each provider's docs, Courier integration examples with provider overrides, and an honest read on where each developer experience holds up and where it breaks down. We also asked Claude to review every API and tell us which one it would wire up first. The answer surprised us.
By Kyle Seyler
February 23, 2026

The Courier MCP Server Is Open Source. Here's How It Actually Works.
Courier's MCP server is open source at github.com/trycourier/courier-mcp. It connects AI coding tools like Cursor and Claude Code to your Courier account so they can send messages, manage users, and install SDKs without hallucinating API details. This post walks through the actual codebase: how 16 tool classes are registered (and how a config allowlist gates most of them), why we pull installation guides from GitHub at runtime instead of bundling them, how the DocsTools class generates live JWTs alongside setup instructions, and what the SdkContextTools class does in the repo to prevent v7/v8 SDK conflicts (even though it isn't wired into the server yet).
By Mike Miller
February 06, 2026
© 2026 Courier. All rights reserved.