<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Kartik Luke Singh</title>
  <subtitle>Engineering notes on Shopify apps, Rails and developer tooling.</subtitle>
  <link href="https://fluke.md/feed.xml" rel="self"/>
  <link href="https://fluke.md/"/>
  <updated>2026-09-29T00:00:00Z</updated>
  <id>https://fluke.md/</id>
  <author><name>Kartik Luke Singh</name><uri>https://fluke.md/</uri></author>
  <entry>
    <title>Shopify Events: only hearing about the updates you care about</title>
    <link href="https://fluke.md/writing/shopify-events-filters/"/>
    <updated>2026-09-29T00:00:00Z</updated>
    <id>https://fluke.md/writing/shopify-events-filters/</id>
    <summary>How we went from about 6 million orders/updated webhooks a day on average to under 500,000, by marking the orders we care about and letting a filtered Shopify Events subscription do the rest.</summary>
    <content type="html">&lt;p&gt;At &lt;a href=&quot;https://artossoftware.com/&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;Artos Software&lt;/span&gt;&lt;/a&gt; we build &lt;a href=&quot;https://www.stoqapp.com/&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;STOQ&lt;/span&gt;&lt;/a&gt;, a Shopify app for pre-orders and back-in-stock alerts. To keep pre-orders and alert-driven orders in sync, we listened to the &lt;code&gt;orders/updated&lt;/code&gt; webhook. The trouble is that Shopify sends that webhook for every change to every order on a shop, and we only care about a sliver of them. So we moved to Shopify&#39;s Events subscriptions,&lt;sup class=&quot;fn&quot;&gt;&lt;a href=&quot;https://fluke.md/writing/shopify-events-filters/#fn1&quot; id=&quot;fnref1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; which run a GraphQL query for each change and can filter deliveries based on the response, and let Shopify throw away the rest before it ever reaches us.&lt;/p&gt;
&lt;h2 id=&quot;why-were-we-getting-6-million-webhooks-a-day&quot;&gt;Why were we getting 6 million webhooks a day?&lt;/h2&gt;
&lt;p&gt;A webhook subscription can&#39;t filter by order. Every edit, tag, fulfilment and refund on every order became a delivery, a queued job, and a database lookup that usually found nothing and returned. Roughly 350 of our largest shops sent over 60% of that traffic. A busy merchant working through orders all day generated millions of jobs that just got ignored. And that&#39;s just the average. Plenty of merchants use bulk-editing apps to tag or update thousands of orders at once, and every one of those edits is another webhook, so a single run can send the count spiking well past it.&lt;/p&gt;
&lt;p&gt;What we actually need are the orders our app created or touched, mostly pre-orders. Those are already rows in our database.&lt;/p&gt;
&lt;h2 id=&quot;so-how-do-you-tell-shopify-which-updates-you-care-about&quot;&gt;So how do you tell Shopify which updates you care about?&lt;/h2&gt;
&lt;p&gt;You mark them, so the marker shows up in the response you filter on. When a buyer adds one of our pre-orders to their cart, we set an attribute on the cart with the Storefront API&#39;s &lt;a href=&quot;https://shopify.dev/docs/api/storefront/latest/mutations/cartAttributesUpdate&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;&lt;code&gt;cartAttributesUpdate&lt;/code&gt;&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;mutation&lt;/span&gt; &lt;span class=&quot;token definition-mutation function&quot;&gt;markCart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable variable-input&quot;&gt;$cartId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token scalar&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property-query property-mutation&quot;&gt;cartAttributesUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;cartId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token variable variable-input&quot;&gt;$cartId&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_tracked_by_stoq&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token object&quot;&gt;cart&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token object&quot;&gt;userErrors&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;field&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Shopify copies cart attributes onto the order it creates, as the order&#39;s custom attributes, so every order that came through our flow arrives already marked.&lt;/p&gt;
&lt;h2 id=&quot;how-do-you-subscribe-to-just-those-updates&quot;&gt;How do you subscribe to just those updates?&lt;/h2&gt;
&lt;p&gt;With an &lt;code&gt;[[events.subscription]]&lt;/code&gt; block in &lt;code&gt;shopify.app.toml&lt;/code&gt;. Shopify runs the GraphQL query for each order change, applies the &lt;code&gt;query_filter&lt;/code&gt; to the response, and only delivers when it matches:&lt;sup class=&quot;fn&quot;&gt;&lt;a href=&quot;https://fluke.md/writing/shopify-events-filters/#fn2&quot; id=&quot;fnref2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;events.subscription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;handle&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;order-tracked&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;topic&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Order&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;update&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;triggers&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;order.*&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;your EventBridge partner source ARN&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&quot;
query order_tracked($orderId: ID!) {
  order(id: $orderId) {
    id
    displayFinancialStatus
    displayFulfillmentStatus
    lineItems(first: 50) { nodes { id quantity } }
    customAttributes { key value }
  }
}
&quot;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;query_filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;order.customAttributes.key:&#39;_tracked_by_stoq&#39;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Shopify evaluates the filter on its side, so an unmarked order never leaves Shopify. Deliveries come through Amazon EventBridge into SQS, and a small translator turns each one back into the shape &lt;code&gt;orders/updated&lt;/code&gt; had, so the existing worker barely changed.&lt;/p&gt;
&lt;figure class=&quot;flow&quot;&gt;&lt;svg viewBox=&quot;0 0 700 270&quot; role=&quot;img&quot; aria-label=&quot;Before: every order change becomes a webhook, a queued job and a worker run that usually ignores it. After: Shopify runs the query and the filter for each order change, and only marked orders reach the worker; unmarked orders never leave Shopify.&quot;&gt;&lt;defs&gt;&lt;marker id=&quot;ah&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;9&quot; refY=&quot;5&quot; markerWidth=&quot;7&quot; markerHeight=&quot;7&quot; orient=&quot;auto-start-reverse&quot;&gt;&lt;path d=&quot;M0,0 L10,5 L0,10 z&quot; class=&quot;ahead&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;/defs&gt;&lt;text class=&quot;flow-l&quot; x=&quot;25&quot; y=&quot;26&quot;&gt;BEFORE&lt;/text&gt;&lt;rect class=&quot;node&quot; x=&quot;25&quot; y=&quot;40&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t&quot; x=&quot;80.0&quot; y=&quot;65&quot; text-anchor=&quot;middle&quot;&gt;order change&lt;/text&gt;&lt;line class=&quot;arrow&quot; x1=&quot;135&quot; y1=&quot;60&quot; x2=&quot;158&quot; y2=&quot;60&quot; marker-end=&quot;url(#ah)&quot;&gt;&lt;/line&gt;&lt;rect class=&quot;node&quot; x=&quot;160&quot; y=&quot;40&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t&quot; x=&quot;215.0&quot; y=&quot;65&quot; text-anchor=&quot;middle&quot;&gt;webhook&lt;/text&gt;&lt;line class=&quot;arrow&quot; x1=&quot;270&quot; y1=&quot;60&quot; x2=&quot;293&quot; y2=&quot;60&quot; marker-end=&quot;url(#ah)&quot;&gt;&lt;/line&gt;&lt;rect class=&quot;node&quot; x=&quot;295&quot; y=&quot;40&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t&quot; x=&quot;350.0&quot; y=&quot;65&quot; text-anchor=&quot;middle&quot;&gt;queue&lt;/text&gt;&lt;line class=&quot;arrow&quot; x1=&quot;405&quot; y1=&quot;60&quot; x2=&quot;428&quot; y2=&quot;60&quot; marker-end=&quot;url(#ah)&quot;&gt;&lt;/line&gt;&lt;rect class=&quot;node&quot; x=&quot;430&quot; y=&quot;40&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t&quot; x=&quot;485.0&quot; y=&quot;65&quot; text-anchor=&quot;middle&quot;&gt;worker&lt;/text&gt;&lt;line class=&quot;arrow&quot; x1=&quot;540&quot; y1=&quot;60&quot; x2=&quot;563&quot; y2=&quot;60&quot; marker-end=&quot;url(#ah)&quot;&gt;&lt;/line&gt;&lt;rect class=&quot;node dim&quot; x=&quot;565&quot; y=&quot;40&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t dim&quot; x=&quot;620.0&quot; y=&quot;65&quot; text-anchor=&quot;middle&quot;&gt;ignored&lt;/text&gt;&lt;text class=&quot;flow-l&quot; x=&quot;25&quot; y=&quot;136&quot;&gt;AFTER&lt;/text&gt;&lt;rect class=&quot;group&quot; x=&quot;188&quot; y=&quot;146&quot; width=&quot;297&quot; height=&quot;72&quot; rx=&quot;6&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-g&quot; x=&quot;200&quot; y=&quot;140&quot;&gt;at Shopify&lt;/text&gt;&lt;rect class=&quot;node&quot; x=&quot;25&quot; y=&quot;162&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t&quot; x=&quot;80.0&quot; y=&quot;187&quot; text-anchor=&quot;middle&quot;&gt;order change&lt;/text&gt;&lt;line class=&quot;arrow&quot; x1=&quot;135&quot; y1=&quot;182&quot; x2=&quot;203&quot; y2=&quot;182&quot; marker-end=&quot;url(#ah)&quot;&gt;&lt;/line&gt;&lt;rect class=&quot;node&quot; x=&quot;205&quot; y=&quot;162&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t&quot; x=&quot;260.0&quot; y=&quot;187&quot; text-anchor=&quot;middle&quot;&gt;query&lt;/text&gt;&lt;line class=&quot;arrow&quot; x1=&quot;315&quot; y1=&quot;182&quot; x2=&quot;358&quot; y2=&quot;182&quot; marker-end=&quot;url(#ah)&quot;&gt;&lt;/line&gt;&lt;rect class=&quot;node&quot; x=&quot;360&quot; y=&quot;162&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t&quot; x=&quot;415.0&quot; y=&quot;187&quot; text-anchor=&quot;middle&quot;&gt;filter&lt;/text&gt;&lt;line class=&quot;arrow&quot; x1=&quot;470&quot; y1=&quot;182&quot; x2=&quot;563&quot; y2=&quot;182&quot; marker-end=&quot;url(#ah)&quot;&gt;&lt;/line&gt;&lt;rect class=&quot;node&quot; x=&quot;565&quot; y=&quot;162&quot; width=&quot;110&quot; height=&quot;40&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;flow-t&quot; x=&quot;620.0&quot; y=&quot;187&quot; text-anchor=&quot;middle&quot;&gt;worker&lt;/text&gt;&lt;text class=&quot;flow-g&quot; x=&quot;517&quot; y=&quot;172&quot; text-anchor=&quot;middle&quot;&gt;marked&lt;/text&gt;&lt;line class=&quot;arrow dim&quot; x1=&quot;415&quot; y1=&quot;202&quot; x2=&quot;415&quot; y2=&quot;238&quot; marker-end=&quot;url(#ah)&quot;&gt;&lt;/line&gt;&lt;text class=&quot;flow-t dim&quot; x=&quot;415&quot; y=&quot;258&quot; text-anchor=&quot;middle&quot;&gt;unmarked: never leaves Shopify&lt;/text&gt;&lt;/svg&gt;&lt;figcaption&gt;Before, every order change reached our workers. After, Shopify runs the query and the filter, and only marked orders get through.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Two rules we learned the hard way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The filter only sees the response, so whatever you filter on has to be &lt;strong&gt;selected&lt;/strong&gt; in the query.&lt;/li&gt;
&lt;li&gt;Subscription queries are capped at 100 complexity points, and nested connection sizes add up fast.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-do-you-move-30-000-shops-over-without-dropping-an-update&quot;&gt;How do you move 30,000 shops over without dropping an update?&lt;/h2&gt;
&lt;p&gt;In three switches per shop, always in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Shadow:&lt;/strong&gt; Events deliveries are recorded and compared with the webhook.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Process:&lt;/strong&gt; Events deliveries are processed for real, alongside the webhook. Duplicates are harmless because the worker is idempotent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cut over:&lt;/strong&gt; the router skips that shop&#39;s webhook, and later we delete the webhook subscription altogether.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The router check leans towards processing whenever it&#39;s unsure:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;skip_webhook_for_events_cutover&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;topic&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;shop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; events_disabled&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; shop&lt;span class=&quot;token operator&quot;&gt;&amp;amp;.&lt;/span&gt;events_enabled&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CUTOVER_TOPICS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;topic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# only skip the webhook once Events is already doing the work&lt;/span&gt;
  processing_events_for&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; topic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; StandardError
  &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# a duplicate is a no-op; a dropped update is unrecoverable&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Leave a gap between steps 2 and 3. Requests already in flight need time to catch up, and if you flip both at once, an update can slip between the webhook you just stopped and the Events delivery that isn&#39;t being processed yet.&lt;/p&gt;
&lt;h2 id=&quot;what-bit-us&quot;&gt;What bit us?&lt;/h2&gt;
&lt;p&gt;The dangerous ones failed silently and looked like success until we compared counts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A search filter Shopify can&#39;t apply matches everything.&lt;/strong&gt; Our hourly reconciler finds marked orders through Admin API order search. When a shop wasn&#39;t set up for that filter, Shopify ignored the term and returned every order, with no error. The canary we use now: search for a value that can&#39;t exist, and expect 0.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A query error drops the delivery.&lt;/strong&gt; If the query touches a field the shop&#39;s token can&#39;t read, Shopify sends a &lt;code&gt;query_errors&lt;/code&gt; payload instead. In shadow we found we were dropping about 80% of order deliveries this way. Now we tolerate partial payloads, and shops missing the scope stay on webhooks.&lt;/p&gt;
&lt;h2 id=&quot;did-it-work&quot;&gt;Did it work?&lt;/h2&gt;
&lt;p&gt;Order-update jobs fell from about 6 million a day on average to under 500,000, a drop of over 90%, and what&#39;s left is mostly orders we actually care about. The volume now scales with our pre-orders, not the merchant&#39;s whole order backlog. An hourly reconciler, searching for the same marker, catches the few changes Events has no trigger for yet.&lt;/p&gt;
&lt;figure class=&quot;chart&quot;&gt;&lt;svg viewBox=&quot;0 0 700 322&quot; role=&quot;img&quot; aria-label=&quot;orders/updated per day, 16 to 30 September. Webhooks run 6.5 to 7.2 million on weekdays and dip at weekends, then fall from 6.48 million on 25 September to about 250 to 290 thousand on 29 and 30 September. Events processed rises from nearly zero to about 200 thousand.&quot;&gt;&lt;rect class=&quot;wkend&quot; x=&quot;174.8&quot; y=&quot;50&quot; width=&quot;44.3&quot; height=&quot;240&quot;&gt;&lt;/rect&gt;&lt;rect class=&quot;wkend&quot; x=&quot;219.0&quot; y=&quot;50&quot; width=&quot;44.3&quot; height=&quot;240&quot;&gt;&lt;/rect&gt;&lt;rect class=&quot;wkend&quot; x=&quot;484.8&quot; y=&quot;50&quot; width=&quot;44.3&quot; height=&quot;240&quot;&gt;&lt;/rect&gt;&lt;rect class=&quot;wkend&quot; x=&quot;529.0&quot; y=&quot;50&quot; width=&quot;44.3&quot; height=&quot;240&quot;&gt;&lt;/rect&gt;&lt;line class=&quot;grid&quot; x1=&quot;64&quot; y1=&quot;290&quot; x2=&quot;684&quot; y2=&quot;290&quot;&gt;&lt;/line&gt;&lt;text class=&quot;axis&quot; x=&quot;54&quot; y=&quot;294&quot; text-anchor=&quot;end&quot;&gt;0M&lt;/text&gt;&lt;line class=&quot;grid&quot; x1=&quot;64&quot; y1=&quot;230&quot; x2=&quot;684&quot; y2=&quot;230&quot;&gt;&lt;/line&gt;&lt;text class=&quot;axis&quot; x=&quot;54&quot; y=&quot;234&quot; text-anchor=&quot;end&quot;&gt;2M&lt;/text&gt;&lt;line class=&quot;grid&quot; x1=&quot;64&quot; y1=&quot;170&quot; x2=&quot;684&quot; y2=&quot;170&quot;&gt;&lt;/line&gt;&lt;text class=&quot;axis&quot; x=&quot;54&quot; y=&quot;174&quot; text-anchor=&quot;end&quot;&gt;4M&lt;/text&gt;&lt;line class=&quot;grid&quot; x1=&quot;64&quot; y1=&quot;110&quot; x2=&quot;684&quot; y2=&quot;110&quot;&gt;&lt;/line&gt;&lt;text class=&quot;axis&quot; x=&quot;54&quot; y=&quot;114&quot; text-anchor=&quot;end&quot;&gt;6M&lt;/text&gt;&lt;line class=&quot;grid&quot; x1=&quot;64&quot; y1=&quot;50&quot; x2=&quot;684&quot; y2=&quot;50&quot;&gt;&lt;/line&gt;&lt;text class=&quot;axis&quot; x=&quot;54&quot; y=&quot;54&quot; text-anchor=&quot;end&quot;&gt;8M&lt;/text&gt;&lt;line class=&quot;mark&quot; x1=&quot;196.9&quot; y1=&quot;50&quot; x2=&quot;196.9&quot; y2=&quot;290&quot;&gt;&lt;/line&gt;&lt;circle class=&quot;mark-dot&quot; cx=&quot;196.9&quot; cy=&quot;42&quot; r=&quot;9&quot;&gt;&lt;/circle&gt;&lt;text class=&quot;mark-n&quot; x=&quot;196.9&quot; y=&quot;46&quot; text-anchor=&quot;middle&quot;&gt;1&lt;/text&gt;&lt;line class=&quot;mark&quot; x1=&quot;506.9&quot; y1=&quot;50&quot; x2=&quot;506.9&quot; y2=&quot;290&quot;&gt;&lt;/line&gt;&lt;circle class=&quot;mark-dot&quot; cx=&quot;506.9&quot; cy=&quot;42&quot; r=&quot;9&quot;&gt;&lt;/circle&gt;&lt;text class=&quot;mark-n&quot; x=&quot;506.9&quot; y=&quot;46&quot; text-anchor=&quot;middle&quot;&gt;2&lt;/text&gt;&lt;line class=&quot;mark&quot; x1=&quot;595.4&quot; y1=&quot;50&quot; x2=&quot;595.4&quot; y2=&quot;290&quot;&gt;&lt;/line&gt;&lt;circle class=&quot;mark-dot&quot; cx=&quot;595.4&quot; cy=&quot;42&quot; r=&quot;9&quot;&gt;&lt;/circle&gt;&lt;text class=&quot;mark-n&quot; x=&quot;595.4&quot; y=&quot;46&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;&lt;polygon class=&quot;area&quot; points=&quot;64.0,74.3 108.3,80.9 152.6,86.9 196.9,114.5 241.1,177.5 285.4,96.8 329.7,89.6 374.0,87.8 418.3,90.2 462.6,95.6 506.9,186.2 551.1,255.8 595.4,269.7 639.7,282.4 684.0,281.3 684.0,290 64.0,290&quot;&gt;&lt;/polygon&gt;&lt;polyline class=&quot;line web&quot; points=&quot;64.0,74.3 108.3,80.9 152.6,86.9 196.9,114.5 241.1,177.5 285.4,96.8 329.7,89.6 374.0,87.8 418.3,90.2 462.6,95.6 506.9,186.2 551.1,255.8 595.4,269.7 639.7,282.4 684.0,281.3&quot;&gt;&lt;/polyline&gt;&lt;polyline class=&quot;line evp&quot; points=&quot;64.0,290.0 108.3,290.0 152.6,290.0 196.9,290.0 241.1,290.0 285.4,289.8 329.7,289.8 374.0,289.7 418.3,289.7 462.6,289.4 506.9,288.9 551.1,287.7 595.4,284.8 639.7,283.8 684.0,283.9&quot;&gt;&lt;/polyline&gt;&lt;text class=&quot;axis&quot; x=&quot;64.0&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;16&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;108.3&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;17&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;152.6&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;18&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;196.9&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;19&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;241.1&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;20&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;285.4&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;21&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;329.7&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;22&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;374.0&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;23&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;418.3&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;24&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;462.6&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;25&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;506.9&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;26&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;551.1&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;27&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;595.4&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;28&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;639.7&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;29&lt;/text&gt;&lt;text class=&quot;axis&quot; x=&quot;684.0&quot; y=&quot;312&quot; text-anchor=&quot;middle&quot;&gt;30&lt;/text&gt;&lt;line class=&quot;line web&quot; x1=&quot;64&quot; y1=&quot;14&quot; x2=&quot;86&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;text class=&quot;axis&quot; x=&quot;94&quot; y=&quot;18&quot;&gt;webhooks&lt;/text&gt;&lt;line class=&quot;line evp&quot; x1=&quot;214&quot; y1=&quot;14&quot; x2=&quot;236&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;text class=&quot;axis&quot; x=&quot;244&quot; y=&quot;18&quot;&gt;Events processed&lt;/text&gt;&lt;/svg&gt;&lt;figcaption&gt;&lt;code&gt;orders/updated&lt;/code&gt; per day, 16–30 September. Shaded columns are weekends. ① first shops cut over · ② the rest of the fleet · ③ old webhook subscriptions deleted.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And that covers moving &lt;code&gt;orders/updated&lt;/code&gt; to Events. Inventory and product updates are next, with the same pattern. If you&#39;re doing something similar, or want to compare notes, say hi on &lt;a href=&quot;https://x.com/0xfluke&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;X&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot; aria-label=&quot;Footnotes&quot;&gt;
&lt;ol&gt;
&lt;li id=&quot;fn1&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://shopify.dev/docs/apps/build/events-webhooks&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;About Events and webhooks&lt;/span&gt;&lt;/a&gt;, and the &lt;a href=&quot;https://shopify.dev/docs/api/events/latest&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;Events API reference&lt;/span&gt;&lt;/a&gt;. &lt;a href=&quot;https://fluke.md/writing/shopify-events-filters/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://shopify.dev/docs/apps/build/events/delivery-structure&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;Events delivery structure&lt;/span&gt;&lt;/a&gt; and &lt;a href=&quot;https://shopify.dev/docs/apps/build/events/delivery-filtering&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;Filter Events deliveries&lt;/span&gt;&lt;/a&gt;. &lt;a href=&quot;https://fluke.md/writing/shopify-events-filters/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Gmail API and Rails: sending emails through ActionMailer</title>
    <link href="https://fluke.md/writing/gmail-api-rails-actionmailer/"/>
    <updated>2019-01-03T00:00:00Z</updated>
    <id>https://fluke.md/writing/gmail-api-rails-actionmailer/</id>
    <summary>How google-http-actionmailer sends ActionMailer mail through the Gmail HTTP API, with token refresh, per-message delivery settings and send hooks.</summary>
    <content type="html">&lt;p&gt;When I was working with my friends on &lt;a href=&quot;https://www.foxbound.io/&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;Foxbound&lt;/span&gt;&lt;/a&gt;, one of the main features we needed for the sales automation tool was to send emails through a user&#39;s Gmail account. Getting this to work in Ruby is easy once you manage to find the documentation. However, I wanted to integrate it with ActionMailer, because it&#39;s a nice structured way of writing mailers and it lets us leverage built-in functionality like the ActiveJob integration to queue up mail for later.&lt;/p&gt;
&lt;p&gt;So that&#39;s how I got around to writing a gem to add Gmail HTTP API support for ActionMailer, which led to the creation of &lt;strong&gt;&lt;a href=&quot;https://github.com/fluke/google-http-actionmailer&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;google-http-actionmailer&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;so-how-do-you-send-mail-through-the-gmail-http-api-in-ruby&quot;&gt;So how do you send mail through the Gmail HTTP API in Ruby?&lt;/h2&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;google-api-client&#39;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;google/apis/gmail_v1&#39;&lt;/span&gt;&lt;/span&gt;

mail &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;

service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Google&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Apis&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;GmailV1&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GmailService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authorization &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; access_token &lt;span class=&quot;token comment&quot;&gt;# You&#39;ll get this through your refresh token&lt;/span&gt;

message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Google&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Apis&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;GmailV1&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;raw&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; mail&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send_user_message&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# ID of the user or &#39;me&#39; for current authenticated user&lt;/span&gt;
  message
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In the gem, this code is incorporated as a custom delivery method that gets included into ActionMailer.&lt;/p&gt;
&lt;h2 id=&quot;what-can-google-http-actionmailer-do&quot;&gt;What can google-http-actionmailer do?&lt;/h2&gt;
&lt;p&gt;The gem is a plug-and-play solution for sending mail through the Google HTTP API for Gmail. It&#39;s easy to configure.&lt;/p&gt;
&lt;p&gt;While working on Foxbound, we also found that we wanted to track the emails we sent and the threads they were in. The message ID and thread ID come back in the response from the Gmail API.&lt;/p&gt;
&lt;p&gt;So we added hooks that trigger on your mail object before and after the email is sent. This lets you write callbacks to store details, trigger actions conditionally, and so on.&lt;/p&gt;
&lt;h2 id=&quot;how-do-i-use-google-http-actionmailer&quot;&gt;How do I use google-http-actionmailer?&lt;/h2&gt;
&lt;p&gt;Set up your authorization as described in the &lt;a href=&quot;https://github.com/google/google-api-ruby-client/#authorization&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;Google API Client&lt;/span&gt;&lt;/a&gt; (the easiest method is to pass a valid access token, which I describe below), then edit &lt;code&gt;config/application.rb&lt;/code&gt; or &lt;code&gt;config/environments/&amp;lt;ENVIRONMENT&amp;gt;.rb&lt;/code&gt; and add or change the ActionMailer configuration:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action_mailer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delivery_method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:google_http_actionmailer&lt;/span&gt;

config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action_mailer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google_http_actionmailer_settings &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;authorization&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;client_options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;application_name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;application_version&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;request_options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;message_options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;content_type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;delivery_options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;before_send&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;after_send&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;For client and request options, see &lt;a href=&quot;https://github.com/google/google-api-ruby-client/blob/master/lib/google/apis/options.rb&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;the options in the Google API client&lt;/span&gt;&lt;/a&gt;. For message options, see the &lt;code&gt;send_user_message&lt;/code&gt; method &lt;a href=&quot;https://github.com/google/google-api-ruby-client/blob/master/generated/google/apis/gmail_v1/service.rb#L1150&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;in the Gmail service&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Normal ActionMailer usage will now be sent using Google&#39;s HTTPS API.&lt;/p&gt;
&lt;p&gt;If you go with access tokens for authorization, you&#39;ll need to know how to refresh them and how to set the token dynamically at the time of sending, which I describe below.&lt;/p&gt;
&lt;h2 id=&quot;refreshing-access-tokens&quot;&gt;Refreshing access tokens&lt;/h2&gt;
&lt;p&gt;This requires the &lt;strong&gt;omniauth-google-oauth2&lt;/strong&gt; gem. We use our existing access token and refresh token to obtain a new token.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OmniAuth&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Strategies&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GoogleOauth2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; strategy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client
token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OAuth2&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AccessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; access_token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;refresh_token&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; refresh_token &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
new_token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;refresh&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Store the new token&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;dynamically-setting-the-delivery-method&quot;&gt;Dynamically setting the delivery method&lt;/h2&gt;
&lt;p&gt;Access tokens usually expire, so you may need to set the delivery method dynamically. For that we use an interceptor. Interceptors are a concept in ActionMailer: they receive the message before it&#39;s sent, so you can use them to modify messages on the way out. In this case we use an interceptor to set the delivery method with a token we refreshed:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DynamicSettingsInterceptor&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delivering_email&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;new_token

    message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delivery_method&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      GoogleHttpActionmailer&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;DeliveryMethod&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token symbol&quot;&gt;authorization&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; token
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    message
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

register_interceptor &lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;DynamicSettingsInterceptor&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;setting-after-send-and-before-send-hooks&quot;&gt;Setting after-send and before-send hooks&lt;/h2&gt;
&lt;p&gt;Sometimes we want the object returned by the Gmail API, perhaps to store details of the email that was sent. For that we can use delivery options: pass a proc that takes two parameters, &lt;code&gt;mail&lt;/code&gt; (the object created by ActionMailer) and &lt;code&gt;message&lt;/code&gt; (the object created by the Gmail API), to the &lt;code&gt;after_send&lt;/code&gt; and &lt;code&gt;before_send&lt;/code&gt; hooks.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action_mailer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delivery_method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:google_http_actionmailer&lt;/span&gt;

config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action_mailer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google_http_actionmailer_settings &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;authorization&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; access_token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;delivery_options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;after_send&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mail&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;before_send&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mail&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;And that covers using &lt;strong&gt;&lt;a href=&quot;https://github.com/fluke/google-http-actionmailer&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;google-http-actionmailer&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;. If you&#39;d like to look at the code, contribute or make suggestions, head over to &lt;a href=&quot;https://github.com/fluke/google-http-actionmailer&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;GitHub&lt;/span&gt;&lt;/a&gt;, or say hi on &lt;a href=&quot;https://x.com/0xfluke&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;X&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Organize your development processes with Overmind</title>
    <link href="https://fluke.md/writing/overmind/"/>
    <updated>2018-10-29T00:00:00Z</updated>
    <id>https://fluke.md/writing/overmind/</id>
    <summary>Run your Procfile with Overmind so every process lives in tmux — connect to a debugger, restart one process, and keep separate Procfiles per workflow.</summary>
    <content type="html">&lt;p&gt;Rarely can a developer get away with running just a single process to get their entire development environment running.&lt;/p&gt;
&lt;p&gt;To start developing our apps we often have to spin up a bunch of processes, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the API server (e.g. rails)&lt;/li&gt;
&lt;li&gt;the client app (e.g. webpack)&lt;/li&gt;
&lt;li&gt;the background job processor (e.g. sidekiq)&lt;/li&gt;
&lt;li&gt;and there could be many others…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We usually run these separately in multiple terminal windows, or run one script that forks the processes, and so on.&lt;/p&gt;
&lt;p&gt;One popular way to tame this jumbled mess of processes is a Procfile — a format to specify the types of processes an application provides and the commands to run them. It&#39;s a standard for Heroku and other platforms-as-a-service.&lt;/p&gt;
&lt;p&gt;A simple Procfile defining a &lt;code&gt;web&lt;/code&gt; process that runs &lt;code&gt;rails server&lt;/code&gt; and a &lt;code&gt;worker&lt;/code&gt; process that runs &lt;code&gt;sidekiq&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rails s
&lt;span class=&quot;token key atrule&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bundle exec sidekiq&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;There are a bunch of tools to run a Procfile, the most famous being &lt;a href=&quot;https://github.com/ddollar/foreman&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;foreman&lt;/span&gt;&lt;/a&gt; by David Dollar, but I&#39;d like to show you how we can take Procfiles to the next level with Overmind.&lt;/p&gt;
&lt;h2 id=&quot;what-is-overmind&quot;&gt;What is Overmind?&lt;/h2&gt;
&lt;p&gt;Frustrated with existing Procfile-based process management tools, &lt;a href=&quot;https://github.com/DarthSim&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;Sergey Alexandrovich&lt;/span&gt;&lt;/a&gt; set out to make a tool that solved his problem. As explained on the &lt;a href=&quot;https://github.com/DarthSim/overmind&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;GitHub project&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The problem with most of those tools is that processes you want to manage start to think they are logging their output into a file, and that can lead to all sorts of problems: severe lagging, losing or breaking colored output. Tools can also add vanity information (unneeded timestamps in logs).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He solved these problems with the project &lt;a href=&quot;https://github.com/DarthSim/hivemind&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;Hivemind&lt;/span&gt;&lt;/a&gt;. However, that wasn&#39;t enough: he wanted processes to be bundled together in their output but still be directly accessible to the developer. He went on to integrate tmux with Hivemind to create Overmind.&lt;/p&gt;
&lt;p&gt;So what can Overmind do that other Procfile managers cannot? Here&#39;s the &lt;a href=&quot;https://github.com/DarthSim/overmind#overmind-features&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;list from the GitHub repository&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It starts processes in a tmux session (more on tmux later), so you can easily connect to any process and gain control over it. This is especially useful because it lets us connect to debuggers.&lt;/li&gt;
&lt;li&gt;It can restart a single process on the fly — you don&#39;t need to restart the whole stack.&lt;/li&gt;
&lt;li&gt;It allows a specified process to die without interrupting all of the others.&lt;/li&gt;
&lt;li&gt;It uses pty to capture process output — so it won&#39;t be clipped or delayed, and it won&#39;t break colored output.&lt;/li&gt;
&lt;li&gt;It can read environment variables from a file and use them as parameters, so you can configure Overmind behavior globally and/or per directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-to-set-up-overmind&quot;&gt;How to set up Overmind&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Overmind supports Linux, *BSD and macOS only.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now that you&#39;ve heard what Overmind can do for you, let&#39;s set it up.&lt;/p&gt;
&lt;p&gt;Overmind requires &lt;a href=&quot;https://github.com/tmux/tmux&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;tmux&lt;/span&gt;&lt;/a&gt;, so install it first:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# on macOS (with Homebrew)&lt;/span&gt;
$ brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; tmux

&lt;span class=&quot;token comment&quot;&gt;# on Ubuntu&lt;/span&gt;
$ &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; tmux&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;On macOS it&#39;s easiest to install Overmind with Homebrew:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; overmind&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Otherwise you can &lt;a href=&quot;https://github.com/DarthSim/overmind#download-the-latest-overmind-release-binary&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;download the latest binary&lt;/span&gt;&lt;/a&gt; or &lt;a href=&quot;https://github.com/DarthSim/overmind#build-overmind-from-source&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;build from source&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you&#39;re done, start it with:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ overmind start &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; path/to/your/Procfile&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;tmux&quot;&gt;tmux&lt;/h2&gt;
&lt;p&gt;The secret ingredient in Overmind is tmux, a terminal multiplexer. It lets you switch between several programs in one terminal, detach them while they keep running in the background, and reattach them to a different terminal. You don&#39;t need to know tmux to use Overmind, but a little knowledge helps.&lt;/p&gt;
&lt;p&gt;A few useful commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Ctrl + b d&lt;/code&gt; — detach from the tmux window once you&#39;re inside with &lt;code&gt;overmind connect&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl + b 0&lt;/code&gt; — each process gets a number in the tmux window, which you can use to move between processes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://fluke.md/assets/posts/overmind/tmux-panes.png&quot; alt=&quot;A tmux session started by Overmind, with the client process in the active pane and the other processes listed along the bottom bar&quot; loading=&quot;lazy&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: my tmux looks different because I&#39;ve customised it. You can customise yours by editing your &lt;code&gt;.tmux.conf&lt;/code&gt;; &lt;a href=&quot;https://github.com/gpakosz/.tmux&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;tmux by gpakosz&lt;/span&gt;&lt;/a&gt; is a good place to start.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;debugging-made-easy-again&quot;&gt;Debugging made easy again&lt;/h2&gt;
&lt;p&gt;When we&#39;re debugging an issue we usually want to use pry or byebug for live debugging. However, with most Procfile managers or other ways of running processes side by side, we can&#39;t reach the debugging console for the process. Overmind lets us connect to any running process in a new window with &lt;code&gt;overmind connect &amp;lt;process name&amp;gt;&lt;/code&gt;. In the picture below, the debugger started on the &lt;code&gt;web&lt;/code&gt; process, and I used &lt;code&gt;overmind connect web&lt;/code&gt; to connect to that process and start debugging.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://fluke.md/assets/posts/overmind/overmind-connect-debugger.png&quot; alt=&quot;Overmind&#39;s combined log on the left and, on the right, overmind connect web attached to a pry session paused in a Rails controller&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;Debugging made easy&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2 id=&quot;restarting-quickly&quot;&gt;Restarting quickly&lt;/h2&gt;
&lt;p&gt;Often we want to restart just one of the processes in our Procfile, perhaps the sidekiq worker, and with foreman we have to close it and all our processes. With Overmind, &lt;code&gt;overmind restart &amp;lt;process name&amp;gt;&lt;/code&gt; restarts just that process.&lt;/p&gt;
&lt;h2 id=&quot;procfiles-for-everything&quot;&gt;Procfiles for everything&lt;/h2&gt;
&lt;p&gt;Generally we don&#39;t have just one stack of processes to run. You might have a separate set to run your Slack bot or to boot your acceptance test server. Since Overmind runs on Procfiles, we can create different sets for each use case, like a &lt;code&gt;Procfile.slack&lt;/code&gt; that boots the event listener and ngrok, or a &lt;code&gt;Procfile.acceptance&lt;/code&gt; that boots your acceptance server and runs the database setup.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Procfile.slack&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bundle exec rake slack&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;listen_for_events
&lt;span class=&quot;token key atrule&quot;&gt;localtunnel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ngrok http 3000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Procfile.dev&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rails s
&lt;span class=&quot;token key atrule&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bundle exec sidekiq&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Procfile.acceptance&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rake acceptance&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;client
&lt;span class=&quot;token key atrule&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rake acceptance&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;server&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If you run multiple Procfiles simultaneously from the same path, you&#39;ll run into this error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;overmind: it looks like Overmind is already running. If it&#39;s not, remove path/to/project/.overmind.sock and try again&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can fix this by giving each set its own socket: &lt;code&gt;overmind start -f Procfile.dev -s ./dev.sock&lt;/code&gt;. Once you start Overmind with a socket, run all your commands with the same socket.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ overmind connect &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; path/to/socket web
$ overmind restart &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; path/to/socket sidekiq
$ overmind &lt;span class=&quot;token function&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; path/to/socket&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Thanks for reading! If you have suggestions or anything else to share, say hi on &lt;a href=&quot;https://x.com/0xfluke&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;X&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>OAuth redirects (and others) in embedded Shopify apps</title>
    <link href="https://fluke.md/writing/oauth-redirects-embedded-shopify-apps/"/>
    <updated>2018-01-15T00:00:00Z</updated>
    <id>https://fluke.md/writing/oauth-redirects-embedded-shopify-apps/</id>
    <summary>Why redirect_to fails inside an embedded Shopify app&#39;s iframe, and how shopify_app&#39;s fullpage_redirect_to breaks out of it.</summary>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2026 note:&lt;/strong&gt; this describes the 2018 embedded app SDK. Embedded apps now use App Bridge, where &lt;code&gt;window.open(url, &amp;quot;_top&amp;quot;)&lt;/code&gt; navigates the top frame. The lesson about reading your gems&#39; source still stands.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Redirecting out of your embedded Shopify app isn&#39;t quite straightforward. I&#39;d integrated Facebook AdInsights data into my app but was unable to get the OAuth request to work within the embedded iframe, as it blocks the request. I&#39;d taken to using a workaround: opening the authentication page in another tab that wasn&#39;t embedded. This was pretty clearly bad UX, but I couldn&#39;t figure my way around it.&lt;/p&gt;
&lt;p&gt;That was until I went through the code of the very useful &lt;a href=&quot;https://github.com/Shopify/shopify_app&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;shopify_app&lt;/span&gt;&lt;/a&gt; gem that I&#39;d been using this whole time and found the &lt;a href=&quot;https://github.com/Shopify/shopify_app/blob/3fb589d71bc03a11a8bb48bf87e613f6ce0210ea/lib/shopify_app/controller_concerns/login_protection.rb#L65&quot; rel=&quot;noopener&quot;&gt;&lt;span class=&quot;txt&quot;&gt;fullpage_redirect_to&lt;/span&gt;&lt;/a&gt; method. On further inspection I found in the Shopify documentation that there was a way to get your request out of the iframe.&lt;/p&gt;
&lt;h2 id=&quot;the-great-escape&quot;&gt;The great escape&lt;/h2&gt;
&lt;p&gt;Since your app is loaded in an iframe, you can&#39;t use the usual &lt;code&gt;redirect_to &#39;auth/facebook&#39;&lt;/code&gt;, assuming you&#39;re using Facebook OmniAuth.&lt;/p&gt;
&lt;p&gt;You have to instead return a page with:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token comment&quot;&gt;// If the current window is the &#39;parent&#39;, change the URL by setting location.href&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/auth/facebook&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// If the current window is the &#39;child&#39;, change the parent&#39;s URL with postMessage&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Shopify.API.remoteRedirect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/auth/facebook&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://myshopname.myshopify.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;on-the-rails&quot;&gt;On the Rails&lt;/h2&gt;
&lt;p&gt;shopify_app implements this in a reusable way. The &lt;code&gt;fullpage_redirect_to&lt;/code&gt; method renders an HTML page if we&#39;re in an embedded app. Otherwise it does a normal redirect.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fullpage_redirect_to&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; ShopifyApp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configuration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;embedded_app&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    render &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;shopify_app/shared/redirect&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;locals&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;current_shopify_domain&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; current_shopify_domain &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
    redirect_to url
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The HTML page contains a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with the parameters for our redirect as data attributes.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;...
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt;
  content_tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;redirection-target&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token symbol&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token symbol&quot;&gt;myshopifyUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;current_shopify_domain&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token symbol&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The JS function picks up these parameters and triggers the redirection script.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DOMContentLoaded&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; redirectTargetElement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;redirection-target&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; targetInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;redirectTargetElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// If the current window is the &#39;parent&#39;, change the URL by setting location.href&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; targetInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// If the current window is the &#39;child&#39;, change the parent&#39;s URL with postMessage&lt;/span&gt;
    normalizedLink &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;a&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    normalizedLink&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; targetInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Shopify.API.remoteRedirect&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; normalizedLink&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; targetInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;myshopifyUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It&#39;s always a good idea to read through the code of the gems you use if you&#39;re having trouble, especially open source ones. Documentation is generally sparse because the maintainers might not have the time.&lt;/p&gt;
</content>
  </entry>
</feed>
