<?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: Hiro</title>
    <description>The latest articles on DEV Community by Hiro (@shikakeworks).</description>
    <link>https://dev.to/shikakeworks</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%2Fuser%2Fprofile_image%2F4236082%2F2309f4e3-15d0-466b-82d3-e8f5c0942fe0.png</url>
      <title>DEV Community: Hiro</title>
      <link>https://dev.to/shikakeworks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shikakeworks"/>
    <language>en</language>
    <item>
      <title>Sending LINE group alerts from a spreadsheet after LINE Notify shut down</title>
      <dc:creator>Hiro</dc:creator>
      <pubDate>Tue, 10 Nov 2026 01:06:44 +0000</pubDate>
      <link>https://dev.to/shikakeworks/sending-line-group-alerts-from-a-spreadsheet-after-line-notify-shut-down-i1</link>
      <guid>https://dev.to/shikakeworks/sending-line-group-alerts-from-a-spreadsheet-after-line-notify-shut-down-i1</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: this post contains an affiliate link to Make. If you sign up through it, I may earn a commission at no extra cost to you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For years, LINE Notify was the easy way to send alerts to a LINE group in Japan. It shut down on March 31, 2025. The replacement is the LINE Messaging API, which takes more setup and has a monthly message limit.&lt;/p&gt;

&lt;p&gt;I used it to send daily deadline alerts from a spreadsheet to a small production team's LINE group. This post covers the setup, how I stayed inside the free plan, what broke, and what I'd change. The project has ended, so this is a look back at a setup that ran for real.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;The automation sends one LINE message a day listing every task that is due today and not done yet. In Japan, many small teams talk in LINE groups, not Slack or email. So the alert goes where people already look.&lt;/p&gt;

&lt;p&gt;The source was a Google Sheet with one row per article. Each row had three steps with a due date and a status: outline done, first draft done, and published.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack and the flow
&lt;/h2&gt;

&lt;p&gt;The stack is Google Sheets, Make and the LINE Messaging API. There are two pieces: a one-time setup to get the group ID, and a daily scenario.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the group ID
&lt;/h3&gt;

&lt;p&gt;You cannot see a LINE group's ID anywhere in the LINE app. You have to catch it from an event.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a LINE Official Account with the Messaging API turned on.&lt;/li&gt;
&lt;li&gt;Point its webhook at a Make custom webhook.&lt;/li&gt;
&lt;li&gt;Invite the bot into the group.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the bot joins, LINE sends a &lt;code&gt;join&lt;/code&gt; event. The group ID is inside that event, in &lt;code&gt;source.groupId&lt;/code&gt;. I copied it once and used it in the daily scenario.&lt;/p&gt;

&lt;h3&gt;
  
  
  The daily scenario
&lt;/h3&gt;

&lt;p&gt;The daily scenario reads the sheet, builds one combined message, and pushes it once:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The daily scenario. One push a day, however many rows are due.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Google Sheets — reads the rows&lt;/li&gt;
&lt;li&gt;Set variable — one nested &lt;code&gt;if()&lt;/code&gt; returns a line only if a step is due today and not done&lt;/li&gt;
&lt;li&gt;Filter — passes only rows where that line exists&lt;/li&gt;
&lt;li&gt;Text aggregator — joins the lines into one message&lt;/li&gt;
&lt;li&gt;HTTP – Make a request — calls the Messaging API push endpoint with the group ID&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The message looked roughly like this (dummy data):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Due today:
- Test article A: first draft
- Test article B: publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;LINE was the tight part. I ran this on the free LINE Official Account plan, which allows up to 200 messages a month in Japan. You cannot buy extra messages on that plan.&lt;/p&gt;

&lt;p&gt;The way LINE counts messages matters here. A push to a group counts once for each person in the group.&lt;/p&gt;

&lt;p&gt;For example, one alert to a five-person group uses five messages, and a month of daily alerts would use about 150. Reply messages do not count, but an alert is a push, so it always counts.&lt;/p&gt;

&lt;p&gt;Make costs were small. The scenario ran inside my Core plan, at 10,000 credits a month for $10.59 (about ¥1,700). That plan covers all my other scenarios too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke and how I fixed it
&lt;/h2&gt;

&lt;p&gt;Three things went wrong, and each had a simple fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Too many messages
&lt;/h3&gt;

&lt;p&gt;At first, the scenario sent one message per overdue row. The group got a burst of separate alerts, and each one ate into the 200-message limit.&lt;/p&gt;

&lt;p&gt;The fix was to aggregate before sending. Set variable builds the line, Filter drops empty rows, and the aggregator joins them.&lt;/p&gt;

&lt;p&gt;Then the push runs once a day. That one change kept the scenario inside the free plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dates typed as text
&lt;/h3&gt;

&lt;p&gt;Some due dates were typed by hand as text, like &lt;code&gt;6/25&lt;/code&gt;. Make could not compare those with today's date.&lt;/p&gt;

&lt;p&gt;I added data validation to the three date columns in Google Sheets, set to "date". After that, nobody could type a text date into those cells.&lt;/p&gt;

&lt;h3&gt;
  
  
  The wrong "today"
&lt;/h3&gt;

&lt;p&gt;Make runs on its own server time. Without a time zone, "today" could be a different day from Japan's today.&lt;/p&gt;

&lt;p&gt;I set the time zone explicitly when formatting dates, like &lt;code&gt;formatDate(now; "YYYY-MM-DD"; "Asia/Tokyo")&lt;/code&gt;. After that, "today" matched the team's calendar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;I would plan around the message limit from the first day. On LINE's free plan, the number of people in the group matters as much as how often you send.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate first.&lt;/strong&gt; One push with everything in it is cheaper than many small ones. Multiple message bubbles in one request do not raise the count either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count the group.&lt;/strong&gt; Multiply the group size by the sends per month before choosing the plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock the date columns.&lt;/strong&gt; Data validation stops text dates before they reach Make.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set the time zone everywhere.&lt;/strong&gt; Any date logic for a team in Japan should say &lt;code&gt;Asia/Tokyo&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to build something like this yourself, you can &lt;a href="https://www.make.com/en/register?pc=shikakeworks&amp;amp;affiliateSource=devto-line-group-alerts-make" rel="noopener noreferrer"&gt;sign up for Make with my link&lt;/a&gt;. It gives you the Core plan free for the first month, and I earn a commission if you stay on a paid plan.&lt;/p&gt;

</description>
      <category>nocode</category>
      <category>automation</category>
      <category>japan</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Make Data store mistake I made twice: blank fields that wipe your records</title>
      <dc:creator>Hiro</dc:creator>
      <pubDate>Tue, 10 Nov 2026 01:02:54 +0000</pubDate>
      <link>https://dev.to/shikakeworks/the-make-data-store-mistake-i-made-twice-blank-fields-that-wipe-your-records-235m</link>
      <guid>https://dev.to/shikakeworks/the-make-data-store-mistake-i-made-twice-blank-fields-that-wipe-your-records-235m</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: this post contains an affiliate link to Make. If you sign up through it, I may earn a commission at no extra cost to you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I made the same Make mistake twice. Both times, a Data store module wrote blank values into fields I never meant to touch.&lt;/p&gt;

&lt;p&gt;The first time, it quietly erased my paid users' subscription data. The second time, I caught it one click before it erased everything.&lt;/p&gt;

&lt;p&gt;This post covers what the system does, what broke, how I fixed it, and the rule I follow now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;It is a subscription-based AI chat service I run on LINE in Japan. Paid members also get an extra request they can use a few times a month. Make handles everything between LINE, the AI model, the payments and the user records.&lt;/p&gt;

&lt;p&gt;The user records live in a Make Data store. One record per user holds the subscription status, the plan, the Stripe customer ID, the chat history, and a counter for those extra requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack and the flow
&lt;/h2&gt;

&lt;p&gt;The stack is Make, LINE, Stripe and an AI model API. The main scenario has 31 modules, and many of them are LINE replies and Router branches.&lt;/p&gt;

&lt;p&gt;The main flow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The main scenario, simplified. The highlighted step is where Bug 1 lived.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;LINE – Watch messages — receives a message&lt;/li&gt;
&lt;li&gt;Data store – Get a record — loads the user's record&lt;/li&gt;
&lt;li&gt;Router — splits by status (free, trial, paid) and by what the user tapped&lt;/li&gt;
&lt;li&gt;HTTP – Make a request — calls the AI model to write the reply&lt;/li&gt;
&lt;li&gt;LINE – Send a reply message — answers the user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data store – Add/replace a record&lt;/strong&gt; — saves the updated chat history&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A separate scenario receives Stripe webhooks and sets each user's status to &lt;code&gt;trial&lt;/code&gt;, &lt;code&gt;paid&lt;/code&gt; or &lt;code&gt;canceled&lt;/code&gt;. The "My page" menu checks whether a Stripe customer ID exists before it opens the billing portal.&lt;/p&gt;

&lt;p&gt;A record looks roughly like this (dummy data):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"user_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"plan_b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"stripe_customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"cus_TEST123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"request_used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"History"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Hello! This is a test message."&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;The whole Make account runs on the Core plan, with 10,000 credits a month. My invoice is $10.59 (about ¥1,700) a month.&lt;/p&gt;

&lt;p&gt;On November 7, I had used 1,734 of 10,000 credits, about 17%. The daily average is around 150 credits, so a full month lands near 4,500. That leaves plenty of room on Core.&lt;/p&gt;

&lt;p&gt;The AI model's API bill for my account was just under ¥1,000 (about $6) for the last 28 days. Fixing these bugs cost nothing extra.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke and how I fixed it
&lt;/h2&gt;

&lt;p&gt;Both bugs came from the same habit: mapping blank values into fields that should have stayed untouched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 1: paid users kept turning back into free users
&lt;/h3&gt;

&lt;p&gt;The symptom was strange. A user paid through Stripe, and the payment was fine, but the Data store kept showing them as free. "My page" also refused to open for them.&lt;/p&gt;

&lt;p&gt;I first suspected a monthly reset scenario, but it was turned off. Then I suspected a scheduled export scenario, but it only reads the Data store and writes to a spreadsheet. That left the scenarios that run on each incoming message.&lt;/p&gt;

&lt;p&gt;The real cause was the last module on the paid-chat branch. It was &lt;strong&gt;Data store – Add/replace a record&lt;/strong&gt; with overwrite turned on, and it mapped only two fields: the chat history and the summary. With overwrite on, every field I did not map was saved as empty.&lt;/p&gt;

&lt;p&gt;So the data was not being lost once a month. It was lost &lt;strong&gt;every time a paid user sent a message&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Status went blank, so the next message fell into the free branch. The customer ID went blank, so "My page" refused them. One module explained every symptom.&lt;/p&gt;

&lt;p&gt;Two more modules had the same problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Update a record&lt;/strong&gt; that adds 1 to the request counter also mapped empty text into status, plan and the customer ID.&lt;/li&gt;
&lt;li&gt;The free-trial branch saved its record without carrying over the Stripe fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How I found it without tracing wires
&lt;/h3&gt;

&lt;p&gt;I did not follow the connections on the canvas by eye. I used &lt;strong&gt;Export Blueprint&lt;/strong&gt; from the scenario's "…" menu and gave the JSON file to an AI (Claude). It listed every module, every mapping and every Router filter, and pointed straight at the three modules.&lt;/p&gt;

&lt;p&gt;Then I had it write a fixed Blueprint and brought it back in with &lt;strong&gt;Import Blueprint&lt;/strong&gt;. Only 3 of the 31 modules changed:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The 3 modules that changed&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paid-chat save: overwrite on, only the chat history and summary mapped&lt;/li&gt;
&lt;li&gt;Request counter update: empty text mapped into status, plan and customer ID&lt;/li&gt;
&lt;li&gt;Free-trial save: Stripe fields not carried over&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paid-chat save: writes back every subscription field from the record it loaded&lt;/li&gt;
&lt;li&gt;Request counter update: maps only the counter, plus 1&lt;/li&gt;
&lt;li&gt;Free-trial save: carries over the Stripe fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the import, I fixed the broken user records in the Data store by hand. The users stayed paid after that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 2: the monthly reset that would have wiped everyone
&lt;/h3&gt;

&lt;p&gt;Three months later, a new problem showed up. The request counter never went back to zero, so users who used their requests last month were still blocked this month.&lt;/p&gt;

&lt;p&gt;The cause was simple. A reset scenario existed, but I had left it turned off. It searches for records where &lt;code&gt;request_used&lt;/code&gt; is 1 or more, then runs &lt;strong&gt;Update a record&lt;/strong&gt; to set it to 0.&lt;/p&gt;

&lt;p&gt;Before turning it on, I exported its Blueprint to check it. That &lt;strong&gt;Update a record&lt;/strong&gt; mapped empty text into every field: status, plan, customer ID, chat history, and the list of requests each user had made. If I had switched it on, the first run would have wiped every paid user who had used a request.&lt;/p&gt;

&lt;p&gt;I changed the mapping so it writes only &lt;code&gt;request_used = 0&lt;/code&gt;. Then I backed up the Data store to a spreadsheet, ran it once, and checked that status, plan and history were still there. They were.&lt;/p&gt;

&lt;p&gt;No one had made a request yet that month, so running it early did not hurt anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;The rule is short: &lt;strong&gt;map only the fields you mean to change.&lt;/strong&gt; In my case, a field left empty in Update a record stayed as it was. A field filled with empty text was overwritten with nothing.&lt;/p&gt;

&lt;p&gt;Here is what I do now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pick the right module.&lt;/strong&gt; I use Update a record to change one or two fields. I use Add/replace only when I want to save the whole record, and then I map every field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit with the Blueprint.&lt;/strong&gt; I export it and ask the AI to list every Data store module that writes empty values. That is faster than opening each module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check off scenarios too.&lt;/strong&gt; A scenario that is turned off can still hold a bad mapping. I read the mapping again before I switch it on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Back up first.&lt;/strong&gt; Before an import or a first run, I export the current Blueprint and copy the Data store to a spreadsheet. Connections are not saved in the Blueprint, so I check them again after the import.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I lost some paid users' subscription data the first time, and almost lost everyone's the second time. Both were the same mistake. Now the mapping check comes before I turn anything on.&lt;/p&gt;

&lt;p&gt;If you want to build something like this yourself, you can &lt;a href="https://www.make.com/en/register?pc=shikakeworks&amp;amp;affiliateSource=devto-data-store-blank-field-trap" rel="noopener noreferrer"&gt;sign up for Make with my link&lt;/a&gt;. It gives you the Core plan free for the first month, and I earn a commission if you stay on a paid plan.&lt;/p&gt;

</description>
      <category>nocode</category>
      <category>automation</category>
      <category>productivity</category>
      <category>make</category>
    </item>
  </channel>
</rss>
