<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SendRelay</title>
    <description>The latest articles on DEV Community by SendRelay (sendrelay-ng).</description>
    <link>https://dev.to/sendrelay-ng</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14963%2F3599e996-073d-41e7-95e8-1445570a9df7.png</url>
      <title>DEV Community: SendRelay</title>
      <link>https://dev.to/sendrelay-ng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sendrelay-ng"/>
    <language>en</language>
    <item>
      <title>No rider accepted your delivery task. What should your app do?</title>
      <dc:creator>SendRelay</dc:creator>
      <pubDate>Mon, 19 Oct 2026 15:46:33 +0000</pubDate>
      <link>https://dev.to/sendrelay-ng/no-rider-accepted-your-delivery-task-what-should-your-app-do-2l82</link>
      <guid>https://dev.to/sendrelay-ng/no-rider-accepted-your-delivery-task-what-should-your-app-do-2l82</guid>
      <description>&lt;p&gt;An order is ready at a pharmacy in Wuse 2, Abuja. The customer opens your app to check on delivery. Your backend has created a Relay task, but no rider accepts the offer. If your screen still says “finding a rider,” the customer has no idea what is happening, and your operations team has no clear next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll build
&lt;/h2&gt;

&lt;p&gt;You'll handle the no-rider branch: store the task ID, observe its terminal state, and show a customer message that your app can actually support. Start by rehearsing it with a test key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RelayClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@relay-sdk/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RelayClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RELAY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// sk_test_... only&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;taskType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PACKAGE_DELIVERY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;autoAssign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;simulationOutcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NO_RIDERS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PICKUP&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;9.0579&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.4951&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo pickup, Wuse 2, Abuja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Simulation only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo parcel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;estimatedValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;estimatedWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LIGHT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;estimatedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BOX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DROPOFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;9.0765&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.3986&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo dropoff, Abuja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Simulation only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Persist task.id with your order. Later, reconcile from Relay:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failureReason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The addresses and coordinates are demonstration inputs. &lt;code&gt;estimatedValue&lt;/code&gt; is the parcel's value in &lt;strong&gt;kobo&lt;/strong&gt;, not the delivery fee. &lt;code&gt;NO_RIDERS&lt;/code&gt; works with &lt;code&gt;sk_test_...&lt;/code&gt; keys; test mode simulates the lifecycle and does not dispatch a real rider or move real money. Follow the &lt;a href="https://docs.sendrelay.com.ng/sdks/node-sdk/" rel="noopener noreferrer"&gt;Node SDK guide&lt;/a&gt; and &lt;a href="https://docs.sendrelay.com.ng/simulation/outcomes/" rel="noopener noreferrer"&gt;simulation outcomes&lt;/a&gt; for setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when nobody accepts?
&lt;/h2&gt;

&lt;p&gt;In the live auto-assignment path, Relay looks for eligible nearby riders in configured offer tiers. A tier can find no eligible riders, receive no deliverable offers, or reach its decision deadline without an accepted assignment. The workflow checks for an assignment before ending the search. If none remains, it marks the task &lt;code&gt;FAILED&lt;/code&gt; with reason &lt;code&gt;NO_RIDERS_AVAILABLE&lt;/code&gt; and emits a task-failed event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg88tub2fbimwsydu7hb7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg88tub2fbimwsydu7hb7.png" alt="Three empty offer trays and silent bells sit beside a parcel under a frosted dome; a loose green thread remains unattached." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That distinction matters in your app. &lt;code&gt;OFFERED&lt;/code&gt; is still in progress; &lt;code&gt;FAILED&lt;/code&gt; is terminal. Don't promise a rider while an offer is pending, and don't keep showing a spinner after the failure. A webhook can synchronize your backend; &lt;code&gt;GET /tasks/{taskId}&lt;/code&gt; lets you reconcile state when a webhook is delayed or your service restarts. Relay also supports WebSocket updates for a connected UI. See the &lt;a href="https://docs.sendrelay.com.ng/tasks/create-task/" rel="noopener noreferrer"&gt;task lifecycle&lt;/a&gt; and &lt;a href="https://docs.sendrelay.com.ng/webhooks/" rel="noopener noreferrer"&gt;webhook guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the customer and your team a useful next step
&lt;/h2&gt;

&lt;p&gt;Back in Wuse 2, your customer should see a plain status such as: “We couldn't find a rider for this delivery. We're checking another option.” That wording is &lt;strong&gt;your product choice&lt;/strong&gt;, not an automatic Relay message. Your operations view should show the failed task and its reason so a person can decide whether to adjust the order, contact the customer, or create a new task. Do not silently treat the failed task as delivered.&lt;/p&gt;

&lt;p&gt;If you retry from your backend, use the documented task-creation idempotency behavior to avoid duplicate task creation from network retries. A new dispatch attempt is a product decision: review the current task state and your payment flow first. Relay's live failure path refunds a wallet-funded task and attempts to void a card preauthorization before publishing failure; your UI should still use the task and payment records as its source of truth rather than promising an instant bank refund.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rehearse the failure branch
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.sendrelay.com.ng/getting-started/get-api-key/" rel="noopener noreferrer"&gt;Get a test API key&lt;/a&gt; and create a task with &lt;code&gt;simulationOutcome: 'NO_RIDERS'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Save the returned &lt;code&gt;task.id&lt;/code&gt; against your order.&lt;/li&gt;
&lt;li&gt;Listen for the task-failed webhook and verify its signature; keep the handler idempotent.&lt;/li&gt;
&lt;li&gt;Retrieve the task by ID to reconcile the final status. Check &lt;code&gt;FAILED&lt;/code&gt; and the failure reason before updating your order and customer UI.&lt;/li&gt;
&lt;li&gt;Repeat with &lt;code&gt;TIER_3_TIMEOUT&lt;/code&gt; to exercise the full timeout path. The &lt;a href="https://docs.sendrelay.com.ng/simulation/outcomes/" rel="noopener noreferrer"&gt;simulation matrix&lt;/a&gt; lists the expected outcomes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When your test task fails, check the customer screen and operations view together. Both should reflect the same failed task before anyone decides what to do with the order. &lt;a href="https://sendrelay.com.ng?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=no-rider-accepted" rel="noopener noreferrer"&gt;Create a Relay account and get API keys&lt;/a&gt;, then use the &lt;a href="https://docs.sendrelay.com.ng/tasks/create-task/" rel="noopener noreferrer"&gt;task creation guide&lt;/a&gt; to run the scenario.&lt;/p&gt;

</description>
      <category>api</category>
      <category>node</category>
      <category>logistics</category>
      <category>nigeria</category>
    </item>
    <item>
      <title>Create a delivery once, even when your Node worker retries</title>
      <dc:creator>SendRelay</dc:creator>
      <pubDate>Wed, 30 Sep 2026 18:42:07 +0000</pubDate>
      <link>https://dev.to/sendrelay-ng/create-a-delivery-once-even-when-your-node-worker-retries-4mlb</link>
      <guid>https://dev.to/sendrelay-ng/create-a-delivery-once-even-when-your-node-worker-retries-4mlb</guid>
      <description>&lt;p&gt;You run an online pharmacy in Wuse 2, Abuja. A customer confirms an order, and your checkout worker starts a delivery. The request times out just as the worker loses its connection. Did the task get created? Retrying with a fresh request can leave your team looking at two dispatches for one order.&lt;/p&gt;

&lt;p&gt;Relay lets your backend attach an idempotency key to task creation. Keep that key tied to the order's delivery intent, and a retry can recover the existing task. The customer sees one delivery; your ops team has one task to track. Here is how to wire that into a Node worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll build
&lt;/h2&gt;

&lt;p&gt;A Node function that creates a package delivery for an Abuja order and safely retries an uncertain result with the same key. You'll use the &lt;a href="https://docs.sendrelay.com.ng/sdks/node-sdk" rel="noopener noreferrer"&gt;Node SDK&lt;/a&gt; and the &lt;a href="https://docs.sendrelay.com.ng/tasks/create-task" rel="noopener noreferrer"&gt;task creation guide&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RelayClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@relay-sdk/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RELAY_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RELAY_API_KEY is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RelayClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createOrderDelivery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Persist the order ID before dispatching this job. Reuse it on every retry.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`order:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:delivery:v1`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;taskType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PACKAGE_DELIVERY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;externalId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PICKUP&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;9.0765&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.3986&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wuse 2, Abuja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ask for the pharmacy desk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Packed order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;estimatedValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;estimatedWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LIGHT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;estimatedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BOX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DROPOFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;9.0579&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.4951&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Garki, Abuja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Call the recipient on arrival&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Store this ID against your order so later events can update the right row.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;duplicate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duplicate&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the package with &lt;code&gt;npm install @relay-sdk/sdk-node&lt;/code&gt;. Keep &lt;code&gt;RELAY_API_KEY&lt;/code&gt; on your server. The SDK sends the explicit key as an &lt;code&gt;Idempotency-Key&lt;/code&gt; header. Its automatically generated POST keys help within a request, but a stable order-based key is what makes a later job retry refer to the same delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retry the same intent, not a new delivery
&lt;/h2&gt;

&lt;p&gt;If the worker times out or its queue redelivers the job, call &lt;code&gt;createOrderDelivery(order)&lt;/code&gt; again with the same persisted order ID and the same delivery details. Do not add a timestamp or random value to the key. Relay looks up a task by account and key; a replay returns the existing task with &lt;code&gt;idempotency.duplicate: true&lt;/code&gt;. The key is for one delivery intent. If the customer requests a genuinely new delivery, use a new versioned key.&lt;/p&gt;

&lt;p&gt;The API describes a 24-hour validity window for this retry flow. Keep your own order-to-task record as the longer-lived source of truth. A timeout does not prove creation failed: retry with the same key, then record the returned task ID. If the API keeps failing, let your queue retry later with that same key rather than issuing an unkeyed create.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it without dispatching a rider
&lt;/h2&gt;

&lt;p&gt;Start with a &lt;code&gt;sk_test_...&lt;/code&gt; key from your &lt;a href="https://dashboard.sendrelay.com.ng/signup?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=retry-safe-delivery-creation" rel="noopener noreferrer"&gt;Relay dashboard&lt;/a&gt;. Test-key task creation runs a simulation: it does not assign a real rider or move real money. The server selects the internal &lt;code&gt;TEST&lt;/code&gt; payment path, so do not send &lt;code&gt;paymentMethod: 'TEST'&lt;/code&gt;. You can pass &lt;code&gt;simulationOutcome: 'SUCCESS'&lt;/code&gt; in the create body while checking the flow. Create the same order twice and check that the second result reports &lt;code&gt;duplicate: true&lt;/code&gt; and the same task ID.&lt;/p&gt;

&lt;p&gt;For the response shape, lifecycle, and test outcomes, see &lt;a href="https://docs.sendrelay.com.ng/tasks/create-task" rel="noopener noreferrer"&gt;Create Task&lt;/a&gt; and &lt;a href="https://docs.sendrelay.com.ng/simulation/outcomes" rel="noopener noreferrer"&gt;Simulation Outcomes&lt;/a&gt;. For your checkout, &lt;a href="https://dashboard.sendrelay.com.ng/signup?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=retry-safe-delivery-creation" rel="noopener noreferrer"&gt;get your API keys&lt;/a&gt; and keep the key generation on your backend.&lt;/p&gt;

</description>
      <category>node</category>
      <category>api</category>
      <category>logistics</category>
      <category>nigeria</category>
    </item>
    <item>
      <title>The first two minutes of a delivery (and everything after)</title>
      <dc:creator>SendRelay</dc:creator>
      <pubDate>Wed, 30 Sep 2026 18:37:57 +0000</pubDate>
      <link>https://dev.to/sendrelay-ng/the-first-two-minutes-of-a-delivery-and-everything-after-5eg4</link>
      <guid>https://dev.to/sendrelay-ng/the-first-two-minutes-of-a-delivery-and-everything-after-5eg4</guid>
      <description>&lt;p&gt;In our &lt;a href="https://dev.to/sendrelay-ng/where-is-my-cake-a-saturday-in-lagos-told-twice-h02"&gt;first post&lt;/a&gt; a cake got across Lagos without anyone making a phone call. In the &lt;a href="https://dev.to/sendrelay-ng/what-would-you-build-if-delivery-were-the-easy-part-10gd"&gt;second&lt;/a&gt;, we looked at the businesses that open up when delivery stops being the hard part.&lt;/p&gt;

&lt;p&gt;People keep asking the same follow-up question: what actually happens in between? You press a button, a rider turns up. What goes on in the middle?&lt;/p&gt;

&lt;p&gt;So let's slow one delivery right down and watch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  10:02 a.m. The order is ready
&lt;/h2&gt;

&lt;p&gt;A pharmacy in Wuse 2, Abuja. A customer in Maitama has ordered a repeat prescription, including insulin, which has to stay cold. The pharmacist has done her checks, packed the order, and taps "ready".&lt;/p&gt;

&lt;p&gt;The price was settled earlier, when the customer checked out. Relay gave the pharmacy's app a quote, and that price held while the order was being prepared. Nobody haggles at the door, and nobody gets a surprise at the end.&lt;/p&gt;

&lt;p&gt;The moment the order is marked ready, it becomes a delivery on Relay, waiting for a rider. The clock starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  10:02 and one second. Who could take this?
&lt;/h2&gt;

&lt;p&gt;Relay looks around the pharmacy for riders who could do this particular job well. Not just anyone nearby: riders who are online right now, who are rated highly enough, whose vehicle suits the trip, and, because of the insulin, who can keep things cold.&lt;/p&gt;

&lt;p&gt;That last part matters. The pharmacy asked for refrigeration when it created the delivery, so a rider without it is never offered the job. It isn't a note someone might forget to read. The rider simply doesn't qualify.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmka65hxvnfhp4rdapfku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmka65hxvnfhp4rdapfku.png" alt="Three waves of offers spreading out from the pharmacy: first the best-placed rider, then up to three riders, then up to twenty" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The first two minutes: three waves
&lt;/h2&gt;

&lt;p&gt;Here's the part most people never see. Relay doesn't shout the job to every rider in the city at once. It asks in waves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first wave goes to one rider&lt;/strong&gt;, the best-placed one, who has thirty seconds to accept. One rider, one clear offer, no scramble.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If they don't take it, the second wave goes to up to three riders&lt;/strong&gt;, for another thirty seconds. Whoever accepts first gets the job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If it's still open, the third wave widens to up to twenty riders&lt;/strong&gt;, for a final minute.&lt;/p&gt;

&lt;p&gt;Those are the default timings. An urgent delivery moves faster, with shorter windows at every wave. Either way, within about two minutes you know where you stand.&lt;/p&gt;

&lt;p&gt;And if nobody accepts? That can happen, especially late at night or in a heavy rainstorm. The delivery is marked as failed, the pharmacy is told straight away, and if the customer paid by card, the amount that was held on it is let go. Nobody's card is charged for a delivery that didn't happen, and the pharmacy isn't left waiting on a promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  10:03 a.m. Someone says yes
&lt;/h2&gt;

&lt;p&gt;A rider called Musa accepts from a few streets away. The delivery is now assigned to him.&lt;/p&gt;

&lt;p&gt;On the customer's phone, the order page changes. It no longer says "preparing". It says a rider is on the way to the pharmacy, and shows him moving on the map.&lt;/p&gt;

&lt;p&gt;This is the moment that used to be a string of phone calls. Now it happens without anyone picking up a phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  10:09 a.m. The pickup
&lt;/h2&gt;

&lt;p&gt;Musa arrives and collects the package. By now the delivery is "in progress", and the pickup is recorded as done, with the time.&lt;/p&gt;

&lt;p&gt;If a delivery has several stops, each one is recorded the same way as it's completed. That's what lets the aso-ebi coordinator from our last post see exactly which of her forty guests have their outfits and which are still waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  10:10 to 10:24 a.m. The part everyone can see
&lt;/h2&gt;

&lt;p&gt;Musa rides towards Maitama. His position updates live, so the customer can watch him come, and the pharmacy can see him from the counter without asking anyone.&lt;/p&gt;

&lt;p&gt;If the customer steps out to buy bread, they can check their phone and know whether to hurry back. If traffic on the way is slow, everyone can see that too. Nobody has to guess, and nobody has to call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6dvq5wk39cge2sidc5qc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6dvq5wk39cge2sidc5qc.png" alt="The life of one delivery: pending, offered, assigned, in progress and completed, with the payment held along the way and released at the end" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10:25 a.m. Delivered
&lt;/h2&gt;

&lt;p&gt;The insulin arrives cold. The delivery is marked complete.&lt;/p&gt;

&lt;p&gt;A few quiet things happen at this moment, and they're the parts businesses end up valuing most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A card payment is collected now, not before.&lt;/strong&gt; If the customer paid by card, the amount was only held when the delivery was created. It's charged once the job is actually done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Musa's earnings are recorded straight away, then held for a short while before they're released.&lt;/strong&gt; That short wait leaves room to raise a problem if something went wrong, before the money has gone anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pharmacy can rate the rider.&lt;/strong&gt; Ratings are part of how riders are matched, so the next business that asks for well-rated riders benefits from this one's feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And everything is written down.&lt;/strong&gt; When the order was ready, when it was offered, who accepted, when the pickup happened, when it arrived, what it cost, and when everyone was paid. If a customer calls next week to ask about their order, the answer is a few seconds away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't happen
&lt;/h2&gt;

&lt;p&gt;It's worth noticing what nobody had to do.&lt;/p&gt;

&lt;p&gt;Nobody rang round for a rider. Nobody negotiated a price at the door. Nobody answered "where is it?" three times. Nobody paid cash and tried to remember it at the end of the month. And the pharmacist never stopped being a pharmacist to become a dispatcher for half an hour.&lt;/p&gt;

&lt;p&gt;That's the whole idea. The delivery still happens in the real world, on real roads, with a real person on a real bike. Relay just takes the uncertainty out of the middle.&lt;/p&gt;

&lt;h2&gt;
  
  
  For the developers reading
&lt;/h2&gt;

&lt;p&gt;Everything above maps to a small, predictable set of states and events, so your app can follow along exactly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A delivery moves through &lt;strong&gt;pending → offered → assigned → in progress → completed&lt;/strong&gt;. It can also end as &lt;strong&gt;failed&lt;/strong&gt; (no rider accepted) or &lt;strong&gt;cancelled&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each change arrives as a webhook your server can listen for: &lt;code&gt;task.status.created&lt;/code&gt;, &lt;code&gt;task.status.offered&lt;/code&gt;, &lt;code&gt;task.status.assigned&lt;/code&gt;, &lt;code&gt;task.status.in_progress&lt;/code&gt;, &lt;code&gt;task.status.completed&lt;/code&gt;, and &lt;code&gt;task.status.failed&lt;/code&gt; or &lt;code&gt;task.status.cancelled&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Every finished stop sends &lt;code&gt;task.stage.completed&lt;/code&gt;, which is handy for multi-stop runs.&lt;/li&gt;
&lt;li&gt;The money has its own events: &lt;code&gt;payment.pending&lt;/code&gt;, &lt;code&gt;payment.released&lt;/code&gt;, &lt;code&gt;payment.disputed&lt;/code&gt;, and &lt;code&gt;payment.dispute_resolved&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Live location for maps comes over a WebSocket, so your customer's screen updates without polling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The part we like most is that you can rehearse all of it before going live. With a test key, you can tell Relay how a delivery should turn out, whether that's a smooth success, a first wave that times out, no riders at all, or a cancellation after the rider accepts, and check that your app handles each one gracefully. The &lt;a href="https://docs.sendrelay.com.ng/simulation/outcomes" rel="noopener noreferrer"&gt;simulation guide&lt;/a&gt; lists every outcome, and our &lt;a href="https://dev.to/sendrelay-ng/create-your-first-delivery-task-with-relay-node-test-mode-34jm"&gt;Node walkthrough&lt;/a&gt; gets you to your first test delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two minutes, and then some
&lt;/h2&gt;

&lt;p&gt;Most of what makes a delivery feel good to a customer happens in the first two minutes and the last one: someone reliable says yes quickly, and the thing arrives the way it left. Everything in between should be visible, fair, and written down.&lt;/p&gt;

&lt;p&gt;That's the job we built Relay to do, so the pharmacist can stay a pharmacist.&lt;/p&gt;

&lt;p&gt;If you'd like Relay behind your deliveries in Lagos or Abuja, start at &lt;a href="https://sendrelay.com.ng/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=devto_first_two_minutes" rel="noopener noreferrer"&gt;sendrelay.com.ng&lt;/a&gt; or write to &lt;a href="mailto:hello@sendrelay.com.ng"&gt;hello@sendrelay.com.ng&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Illustrations by Relay. The pharmacy, the customer and Musa are an illustrative example, not a real delivery.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nigeria</category>
      <category>startup</category>
      <category>api</category>
      <category>logistics</category>
    </item>
    <item>
      <title>What would you build if delivery were the easy part?</title>
      <dc:creator>SendRelay</dc:creator>
      <pubDate>Mon, 28 Sep 2026 15:34:28 +0000</pubDate>
      <link>https://dev.to/sendrelay-ng/what-would-you-build-if-delivery-were-the-easy-part-10gd</link>
      <guid>https://dev.to/sendrelay-ng/what-would-you-build-if-delivery-were-the-easy-part-10gd</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/sendrelay-ng/where-is-my-cake-a-saturday-in-lagos-told-twice-h02"&gt;our first post&lt;/a&gt;, we followed one cake from Yaba to Lekki. This time, let's zoom out.&lt;/p&gt;

&lt;p&gt;Look closely at almost any business in Lagos or Abuja and you'll find a small delivery company hiding inside it. The pharmacy has a boy on a bike. The tailor has a cousin who "knows a rider". The caterer borrows her brother's car on Saturdays. None of them set out to run logistics, but every one of them does, a little badly, every day.&lt;/p&gt;

&lt;p&gt;Relay exists so they don't have to. And once moving things across a city stops being the hard part, a lot of ideas that used to be too much trouble start to look like good businesses.&lt;/p&gt;

&lt;p&gt;Here are some of them. A few are obvious. Most, we hope, are not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz1qozzftgbn6l7p29wju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz1qozzftgbn6l7p29wju.png" alt="Five shapes a delivery can take: across town now; one pickup, many doors; many pickups, one door; there and back; and people, not parcels" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the ones you'd expect
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Restaurants and cloud kitchens.&lt;/strong&gt; The order is packed, a nearby rider is offered the job, and your customer watches the food come to them instead of calling to ask.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pharmacies.&lt;/strong&gt; Medicine goes out the door with a rider you can see on a map. Anything that needs to stay cold can be matched only with riders who can keep it cold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Online shops.&lt;/strong&gt; Whether you sell on a website or through Instagram, your customer can see the delivery price before they pay, and it won't change halfway there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Groceries and errands.&lt;/strong&gt; Someone else does the market run. You follow it from your phone.&lt;/p&gt;

&lt;p&gt;These are the reasons most people come to Relay. The interesting part is what else becomes possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Across town, right now
&lt;/h2&gt;

&lt;p&gt;Some deliveries are small and urgent. They don't need a van; they need someone reliable, quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The contract that must be signed today.&lt;/strong&gt; A law firm in Ikoyi needs a signed agreement at a bank in Victoria Island before three o'clock. It goes out as an urgent job, only to riders above a rating you choose, or only to riders from a fleet you trust. Everyone involved can see where the envelope is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The part that gets the car moving again.&lt;/strong&gt; A driver breaks down on the Lekki-Epe Expressway. The mechanic knows exactly which part he needs, and it's sitting in a shop at Ladipo. An app that connects roadside mechanics to parts dealers, and sends the part out the moment it's paid for, is a real business. It only ever needed a delivery network that nobody could afford to build alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The thing you forgot at home.&lt;/strong&gt; Your laptop charger. Your ID card. The keys your sister needs. A simple "send anything across town" app, built for the everyday version of this problem, is maybe a weekend of work on top of Relay.&lt;/p&gt;

&lt;h2&gt;
  
  
  One pickup, many doors
&lt;/h2&gt;

&lt;p&gt;A single Relay delivery can have several stops, and that one detail opens more doors than you'd expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aso-ebi season.&lt;/strong&gt; One tailor, forty guests, forty outfits, all due before the wedding on Saturday. Instead of forty phone calls and a very long week, it's one pickup and a list of drop-offs, and each guest can see when theirs is on the way. Anyone who has ever coordinated aso-ebi for a family wedding is already smiling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1768212565426-58b089b6386d%3Fw%3D1200%26h%3D675%26fit%3Dcrop%26crop%3Dentropy%26fm%3Djpg%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1768212565426-58b089b6386d%3Fw%3D1200%26h%3D675%26fit%3Dcrop%26crop%3Dentropy%26fm%3Djpg%26q%3D80" alt="Rolls of bright ankara fabric stacked on a market table" width="1200" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subscriptions that go out on a round.&lt;/strong&gt; Weekly zobo and smoothies. Meal prep for busy professionals. Fresh bread every Friday morning. A small subscription business lives or dies on getting the same thing to the same thirty doors, reliably, every week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Giving, delivered.&lt;/strong&gt; Churches, mosques, and community groups that send food packs to families can plan a run of drop-offs and know that every stop was reached, with a record to show for it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1785094165709-54fef69c2c4d%3Fw%3D1200%26h%3D675%26fit%3Dcrop%26crop%3Dentropy%26fm%3Djpg%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1785094165709-54fef69c2c4d%3Fw%3D1200%26h%3D675%26fit%3Dcrop%26crop%3Dentropy%26fm%3Djpg%26q%3D80" alt="A busy Lagos market seen from above, with umbrellas, stalls, cars and shoppers" width="1200" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Many pickups, one door
&lt;/h2&gt;

&lt;p&gt;Flip it around. Several stops to collect, one place to deliver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A personal shopper for Balogun.&lt;/strong&gt; The fabric from one stall, the lace from another, the shoes from a third. A shopper walks the market, and one rider collects from every stall and brings it all to one customer. For someone in Abuja, or just someone who can't face the market, that's a service worth paying for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The morning ingredient run.&lt;/strong&gt; A restaurant buying fresh produce from Mile 12 every morning. Peppers from one seller, onions from another, everything to the kitchen before lunch service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event day.&lt;/strong&gt; The cake from Yaba, the flowers from Obalende, the small chops from Surulere, all arriving at the venue before the guests do.&lt;/p&gt;

&lt;h2&gt;
  
  
  There and back
&lt;/h2&gt;

&lt;p&gt;Some ideas only work if the thing comes back. Those used to be twice as hard. With Relay they're simply two deliveries: one out, one home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laundry and dry cleaning&lt;/strong&gt; that collects on Monday and returns on Thursday, without the business ever buying a van.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phone and laptop repairs.&lt;/strong&gt; The cracked phone goes to the technician; the fixed phone comes back. The customer never has to sit in traffic to Computer Village.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Renting instead of buying.&lt;/strong&gt; Cameras for a weekend shoot. A PlayStation for the holidays. A dress for one wedding. Baby gear that's only needed for a few months. Rental businesses are held back by one question: how does it get there, and how does it come back? Answer that, and a whole category of small businesses opens up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1529171918672-ba6d0733a56c%3Fw%3D1200%26h%3D675%26fit%3Dcrop%26crop%3Dentropy%26fm%3Djpg%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1529171918672-ba6d0733a56c%3Fw%3D1200%26h%3D675%26fit%3Dcrop%26crop%3Dentropy%26fm%3Djpg%26q%3D80" alt="The view from inside a keke, looking out at motorcycles, cars and pedestrians on a busy road" width="1200" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle with care
&lt;/h2&gt;

&lt;p&gt;Not everything should travel on the back of an open bike. When you create a delivery, you can ask for the right kind of vehicle: a bicycle, motorcycle, keke, car, or truck. You can also require a box, air conditioning, or refrigeration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold chain for small producers.&lt;/strong&gt; A poultry farmer in Ikorodu, a fishmonger in Epe, an ice cream maker in Wuse. Refrigerated deliveries mean small producers can sell directly to restaurants and homes without owning a cold truck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Home sample collection.&lt;/strong&gt; A diagnostic lab that collects samples from patients at home and brings them back, kept cold, on a route the lab can see. The lab still follows its own clinical rules. Relay moves the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fragile and beautiful things.&lt;/strong&gt; Cakes, flowers, artwork. Air conditioning and a proper box make the difference between arriving perfect and arriving sad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heavy things.&lt;/strong&gt; Cement and tiles to a building site. A finished wardrobe from the carpenter's workshop to the new flat. Bulk deliveries by truck, followed on a map by the site manager who is waiting for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  People, not parcels
&lt;/h2&gt;

&lt;p&gt;Relay can move people too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The night shift.&lt;/strong&gt; Hospitals, restaurants, call centres, and hotels all have staff who finish work when it isn't safe or easy to get home. A staff ride service, using your own trusted drivers in air-conditioned cars, with every trip on record, is the kind of care your staff will remember.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guests.&lt;/strong&gt; A small hotel or guesthouse that offers airport pickups, with the guest watching their driver approach instead of wondering whether anyone is coming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring your own riders
&lt;/h2&gt;

&lt;p&gt;Maybe you already have riders.&lt;/p&gt;

&lt;p&gt;A pharmacy chain with ten riders of its own, a fleet owner with twenty bikes, a logistics company that has done things by phone for years: all of them can run their own people through Relay. You can keep a job to your own fleet, or open it to independent riders when you're busy. You can see which riders are free nearby and pick one yourself. Our rider engine is open source, so a fleet can build a rider app of its own on the same tracking Relay uses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz1g2el6ofcnwarn1tyrw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz1g2el6ofcnwarn1tyrw.png" alt="What Relay takes care of: the right rider, special care, price first, live on the map, payment held, and your own riders too" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern behind all of these
&lt;/h2&gt;

&lt;p&gt;Every idea above has the same bones. Something needs to get from one part of the city to another. Someone is waiting for it. Someone needs to trust that it went well.&lt;/p&gt;

&lt;p&gt;Relay takes care of that part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it finds a suitable rider nearby, on the right kind of vehicle&lt;/li&gt;
&lt;li&gt;it agrees the price before anyone moves&lt;/li&gt;
&lt;li&gt;it shows everyone where the delivery is, live&lt;/li&gt;
&lt;li&gt;it holds the payment until the job is done&lt;/li&gt;
&lt;li&gt;it keeps an honest record of every step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That leaves you free to work on the part that's actually yours: the idea itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your turn
&lt;/h2&gt;

&lt;p&gt;If you want to find an idea of your own, try this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where do people around you wait for something to arrive?&lt;/li&gt;
&lt;li&gt;What do you regularly drive across town to fetch, or ask someone else to fetch for you?&lt;/li&gt;
&lt;li&gt;Which business near you still says "sorry, we don't deliver"?&lt;/li&gt;
&lt;li&gt;What would people rent, share, or subscribe to if getting it to them were easy?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Somewhere in those answers is a business that would have been too much trouble last year.&lt;/p&gt;

&lt;p&gt;A note on honesty: Relay is live in Lagos and Abuja for now. It moves things and people between places. Your business still does its own part, whether that's checking a prescription, following a lab's procedures, or deciding what your customers are allowed to send.&lt;/p&gt;

&lt;p&gt;If you build software, &lt;a href="https://dev.to/sendrelay-ng/create-your-first-delivery-task-with-relay-node-test-mode-34jm"&gt;our step-by-step guide&lt;/a&gt; walks you through your first delivery in a few lines of code. If you run a business, write to &lt;a href="mailto:hello@sendrelay.com.ng"&gt;hello@sendrelay.com.ng&lt;/a&gt; and tell us what you're trying to move. And if one of these ideas sparked a better one, tell us in the comments. We'd love to hear it.&lt;/p&gt;

&lt;p&gt;Start at &lt;a href="https://sendrelay.com.ng/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=devto_use_cases" rel="noopener noreferrer"&gt;sendrelay.com.ng&lt;/a&gt;.&lt;br&gt;
Next: &lt;a href="https://dev.to/sendrelay-ng/the-first-two-minutes-of-a-delivery-and-everything-after-5eg4"&gt;The first two minutes of a delivery&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Illustrations by Relay. Photos from Unsplash: Lagos market by &lt;a href="https://unsplash.com/photos/IWMQ5P9HxYM" rel="noopener noreferrer"&gt;Fahd Aminu&lt;/a&gt;, ankara fabrics by &lt;a href="https://unsplash.com/photos/toQSUFO1hSA" rel="noopener noreferrer"&gt;WyteShot&lt;/a&gt;, keke view by &lt;a href="https://unsplash.com/photos/t6nqZ0n3i-k" rel="noopener noreferrer"&gt;Joshua Oluwagbemiga&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>nigeria</category>
      <category>business</category>
      <category>ideas</category>
    </item>
    <item>
      <title>Create your first delivery task with Relay (Node, test mode)</title>
      <dc:creator>SendRelay</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:01:32 +0000</pubDate>
      <link>https://dev.to/sendrelay-ng/create-your-first-delivery-task-with-relay-node-test-mode-34jm</link>
      <guid>https://dev.to/sendrelay-ng/create-your-first-delivery-task-with-relay-node-test-mode-34jm</guid>
      <description>&lt;h1&gt;
  
  
  Create your first delivery task with Relay (Node, test mode)
&lt;/h1&gt;

&lt;p&gt;New to Relay? Start with &lt;a href="https://dev.to/sendrelay-ng/where-is-my-cake-a-saturday-in-lagos-told-twice-h02"&gt;the story behind it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Imagine you run a pharmacy app in Lagos. An order is packed, but your checkout has no delivery ID to show the customer. Your team could call riders one by one and answer “where is my order?” from a chat thread. Instead, your backend can hand the delivery to Relay when the order is ready, save the task ID, and use that ID to follow its progress. In test mode, you can build that handoff without dispatching a real rider.&lt;/p&gt;

&lt;p&gt;In this tutorial, you'll create a simulated package delivery with Relay's Node SDK. You'll install the SDK, use a test API key, send pickup and dropoff stages, and retrieve the task you created. The same pattern gives your order system a stable reference for later webhook handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll build
&lt;/h2&gt;

&lt;p&gt;This is the core call. It creates a test task with a deliberate idempotency key, so a retry for the same order does not create another intended delivery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RelayClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@relay-sdk/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RelayClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RELAY_API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;taskType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PACKAGE_DELIVERY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;simulationOutcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;autoAssign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PICKUP&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;6.5244&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.3792&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo pickup, Lagos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Simulation only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo parcel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;estimatedValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;estimatedWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STANDARD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;estimatedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BOX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DROPOFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;6.4654&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.4064&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo dropoff, Lagos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Simulation only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-demo-001-delivery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coordinates and addresses above are demonstration inputs. &lt;code&gt;estimatedValue&lt;/code&gt; is the parcel's declared value in kobo; it is not a delivery quote. Relay's &lt;a href="https://docs.sendrelay.com.ng/tasks/create-task/" rel="noopener noreferrer"&gt;create-task guide&lt;/a&gt; covers the request fields in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get a test key
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sendrelay.com.ng/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=devto_first_task" rel="noopener noreferrer"&gt;Create a Relay account and get an API key&lt;/a&gt;. Use a key that starts with &lt;code&gt;sk_test_&lt;/code&gt;; the current SDK and &lt;a href="https://docs.sendrelay.com.ng/getting-started/get-api-key/" rel="noopener noreferrer"&gt;API-key guide&lt;/a&gt; use that prefix for test mode. Keep the key on your server and out of source control. Never put it in browser code.&lt;/p&gt;

&lt;p&gt;For a local Node project, install the SDK and put your key in your shell environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @relay-sdk/sdk-node
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RELAY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'sk_test_YOUR_TEST_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The placeholder is not a real credential. If you use another shell or hosting platform, set the environment variable through its secret settings. The SDK sends the key in the &lt;code&gt;X-Relay-Key&lt;/code&gt; header; you do not need to build that header yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the task
&lt;/h2&gt;

&lt;p&gt;Save the first code block as &lt;code&gt;create.mjs&lt;/code&gt;, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node create.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use Node 18 or newer, as required by the Node SDK. A successful call prints the task ID and its initial status. Save &lt;code&gt;created.task.taskId&lt;/code&gt; against your own order ID. Task creation means the delivery request was accepted; it does not mean delivery is complete.&lt;/p&gt;

&lt;p&gt;The request has two stages. &lt;code&gt;PICKUP&lt;/code&gt; tells Relay where the parcel begins; &lt;code&gt;DROPOFF&lt;/code&gt; tells it where the parcel should end. &lt;code&gt;simulationOutcome: 'SUCCESS'&lt;/code&gt; selects the success path for a test task. Test mode simulates the lifecycle without a real rider or real payment. You can use &lt;a href="https://docs.sendrelay.com.ng/simulation/outcomes/" rel="noopener noreferrer"&gt;simulation outcomes&lt;/a&gt; to exercise other branches after the basic path works.&lt;/p&gt;

&lt;p&gt;The second argument to &lt;code&gt;relay.tasks.create()&lt;/code&gt; sets &lt;code&gt;idempotencyKey&lt;/code&gt;. Derive this from a stable order ID in your app. If a network timeout makes you uncertain whether the create succeeded, retry the same intended delivery with the same key. For a different order, use a different key. The SDK can generate a key automatically, but an explicit order-based key gives your application control over retries.&lt;/p&gt;

&lt;p&gt;Do not add &lt;code&gt;paymentMethod: 'TEST'&lt;/code&gt; to this request. The test key selects the test flow. When you build your real order workflow, review the &lt;a href="https://docs.sendrelay.com.ng/tasks/create-task/" rel="noopener noreferrer"&gt;task creation guide&lt;/a&gt; and your payment setup before switching to a live key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the task back
&lt;/h2&gt;

&lt;p&gt;The task ID is the link between your order and Relay's delivery state. Add this after the create call to fetch the task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A test simulation can progress after the create response, so the status you read immediately may still be an early lifecycle state. Store the ID, then use task retrieval when you need the current snapshot. The &lt;a href="https://docs.sendrelay.com.ng/sdks/node-sdk/" rel="noopener noreferrer"&gt;Node SDK guide&lt;/a&gt; describes the task methods and response shape.&lt;/p&gt;

&lt;p&gt;For an application that must update a customer or operations screen when the status changes, subscribe to Relay's &lt;a href="https://docs.sendrelay.com.ng/webhooks/" rel="noopener noreferrer"&gt;webhook events&lt;/a&gt;. Verify each webhook's signature against the raw request body before using its data, and make event handling safe to repeat. Your order database should record the Relay task ID and the event IDs it has already processed. This is how the same Lagos order can move from “ready” to a delivery state your team can inspect without manual rider calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the next slice
&lt;/h2&gt;

&lt;p&gt;Once the success simulation works, create a new test order using &lt;code&gt;simulationOutcome: 'NO_RIDERS'&lt;/code&gt;. Decide what your checkout and support team should see when there is no available rider. Then connect the webhook to your order state and test duplicate delivery of an event before you use a live key.&lt;/p&gt;

&lt;p&gt;You now have a small, repeatable delivery integration: an order triggers one Relay task, your app saves its ID, and you can inspect its status. &lt;a href="https://sendrelay.com.ng/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=devto_first_task" rel="noopener noreferrer"&gt;Get your test API key&lt;/a&gt; and follow the &lt;a href="https://docs.sendrelay.com.ng/getting-started/make-first-request/" rel="noopener noreferrer"&gt;first-request guide&lt;/a&gt; to try it in your backend.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@ib_daye" rel="noopener noreferrer"&gt;@ib_daye&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/041h4gQImTM" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>node</category>
      <category>javascript</category>
      <category>nigeria</category>
    </item>
    <item>
      <title>"Where is my cake?" A Saturday in Lagos, told twice</title>
      <dc:creator>SendRelay</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:01:26 +0000</pubDate>
      <link>https://dev.to/sendrelay-ng/where-is-my-cake-a-saturday-in-lagos-told-twice-h02</link>
      <guid>https://dev.to/sendrelay-ng/where-is-my-cake-a-saturday-in-lagos-told-twice-h02</guid>
      <description>&lt;p&gt;Say you bake cakes in Yaba.&lt;/p&gt;

&lt;p&gt;It stopped being a hobby a while ago. The oven is on by six, your phone buzzes with orders all day, and on Saturdays there is barely room on the table for the boxes. People find you on Instagram, fall for a photo, and send a message: &lt;em&gt;Can you do a two-tier for my mum's birthday? Lekki. Saturday, 3pm.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You say yes. Of course you say yes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1741863175748-bb73eb5056f1%3Fw%3D1200%26fm%3Djpg%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1741863175748-bb73eb5056f1%3Fw%3D1200%26fm%3Djpg%26q%3D80" alt="A smiling baker in an apron stands behind a finished pink cake" width="1200" height="1800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The cake is the easy part
&lt;/h2&gt;

&lt;p&gt;By noon on Saturday the cake is done, and it is lovely. Now the real work starts.&lt;/p&gt;

&lt;p&gt;You call the rider you used last week. He doesn't pick up. You call another one, who says he's close, then names a price well above what you told the customer delivery would cost. You agree anyway, because it's already 12:40. He says he's coming. Twenty minutes later, he is still coming.&lt;/p&gt;

&lt;p&gt;When he finally arrives, you hand over the box, say a small prayer, and watch him ride off into Lagos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1648023199223-25d3622bcb13%3Fw%3D1200%26fm%3Djpg%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1648023199223-25d3622bcb13%3Fw%3D1200%26fm%3Djpg%26q%3D80" alt="Buses, cars and crowds packed into Idumota, Lagos Island, seen from above" width="1200" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the calls begin.&lt;/p&gt;

&lt;p&gt;At 1:30 the customer asks if the cake has left. At 2:15 she asks where it is. You call the rider. "I dey Third Mainland, I dey come." At 2:50 she calls again. The guests are arriving, and you have no honest answer for her, because you don't know where your cake is. All you have is his word.&lt;/p&gt;

&lt;p&gt;The cake arrives at 3:25. It's fine, thank God. Her afternoon wasn't, and neither was yours. You spent an hour and a half as a switchboard. You paid the rider in cash and have nothing written down to show for it. Tomorrow, you'll do it all again.&lt;/p&gt;

&lt;p&gt;If any of that felt familiar, this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now play it again
&lt;/h2&gt;

&lt;p&gt;Same Saturday. Same cake. This time your orders come in through a website, and behind that website sits Relay.&lt;/p&gt;

&lt;p&gt;At noon you mark the order as ready, and that's it. Relay looks for a rider near your shop, one with good ratings and the right kind of vehicle for a cake that can't be tipped on its side, and offers him the job. You haven't made a single call.&lt;/p&gt;

&lt;p&gt;The price was settled before anyone moved. Your customer saw it when she ordered, and it doesn't creep up halfway across the bridge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1761370980969-a803951cf104%3Fw%3D1200%26fm%3Djpg%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1761370980969-a803951cf104%3Fw%3D1200%26fm%3Djpg%26q%3D80" alt="A young woman in a white T-shirt checks her phone on a street in Abuja" width="1200" height="1800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;She doesn't need to call you, because she can see for herself. The page she ordered from shows the rider moving: picked up, on the bridge, turning into her street. When her friends ask whether the cake is coming, she turns her phone around and shows them.&lt;/p&gt;

&lt;p&gt;The money is held safely while the delivery happens and settled once the job is done. Every step is recorded, so at the end of the month you aren't trying to remember which rider you paid, or how much.&lt;/p&gt;

&lt;p&gt;The cake goes to the same address either way. What changes is the afternoon you both get to have.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what is Relay?
&lt;/h2&gt;

&lt;p&gt;Relay is the delivery engine behind apps and websites that sell things people need brought to their door. It isn't an app you download. It's the part underneath that finds the rider, fixes the price, shows everyone where the delivery is, and keeps an honest record of what happened.&lt;/p&gt;

&lt;p&gt;We're starting in Lagos and Abuja.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to reach for Relay
&lt;/h2&gt;

&lt;p&gt;You'll know it's time when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;someone on your team spends part of every day calling riders&lt;/li&gt;
&lt;li&gt;customers ask "where is my order?" more often than they say thank you&lt;/li&gt;
&lt;li&gt;you've paid for a delivery and been left with nothing but a phone number&lt;/li&gt;
&lt;li&gt;you're building an app or website for food, medicine, groceries, errands, or anything else that has to reach a doorstep&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run a business, mention Relay to whoever builds your website or app, or write to us at &lt;a href="mailto:hello@sendrelay.com.ng"&gt;hello@sendrelay.com.ng&lt;/a&gt; and we'll talk it through with you. If you're the one who builds things, &lt;a href="https://dev.to/sendrelay-ng/create-your-first-delivery-task-with-relay-node-test-mode-34jm"&gt;the next post on this page&lt;/a&gt; shows you how to send your first delivery with a few lines of code.&lt;/p&gt;

&lt;p&gt;Either way, start at &lt;a href="https://sendrelay.com.ng/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=devto_intro" rel="noopener noreferrer"&gt;sendrelay.com.ng&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The cake deserves to arrive calmly. So do you.&lt;/p&gt;

&lt;p&gt;Next: &lt;a href="https://dev.to/sendrelay-ng/what-would-you-build-if-delivery-were-the-easy-part-10gd"&gt;What would you build if delivery were the easy part?&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Photos from Unsplash: cover by &lt;a href="https://unsplash.com/photos/041h4gQImTM" rel="noopener noreferrer"&gt;ib daye&lt;/a&gt;, baker by &lt;a href="https://unsplash.com/photos/18b_PadpaVc" rel="noopener noreferrer"&gt;WyteShot&lt;/a&gt;, Idumota by &lt;a href="https://unsplash.com/photos/cFT_Xq4XyA0" rel="noopener noreferrer"&gt;Opeyemi Adisa&lt;/a&gt;, Abuja by &lt;a href="https://unsplash.com/photos/HSpbCiZn8Co" rel="noopener noreferrer"&gt;Muhammad-Taha Ibrahim&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nigeria</category>
      <category>startup</category>
      <category>smallbusiness</category>
      <category>logistics</category>
    </item>
  </channel>
</rss>
