Twitter (X) to SMS: Get a Text Message When an Account Posts

By TwitterAPI.io Team•22 min read
Pipeline diagram of a Twitter (X) to SMS alert. Left: X accounts such as @NWS, @FEMA, @NASA and @SpaceX, up to about 25 per query. Then TwitterAPI.io polling with /twitter/tweet/advanced_search, query (from:NWS OR from:FEMA) -filter:replies since_time:<unix>, an empty pass costs $0.00015. Then your script: drop seen ids, oldest first, cut the body to 300 characters and keep the link, cron every 1 or 5 minutes. Then Twilio to phone: POST /2010-04-01/Accounts/{sid}/Messages.json with To, From, Body at $0.0083 a segment. Footer: measured on 1,000 posts from 10 organization accounts collected in 2026, 39.1% were replies or reposts a subscriber would probably not want texted; the original posts of all 10 accounts come to about 1,356 texts a month, about $27.65 in SMS at Twilio's US price.
The whole pipeline: one search call finds new posts, your script de-duplicates and trims, one Twilio call sends the text. Diagram; the footer numbers come from the study below.

"Twitter to SMS" has meant three different things over the years, and the search results still mix them up: posting a tweet by sending a text to a short code, X's own text notifications when someone you follow posts, and the thing most people searching today actually want, which is a text on their phone when a specific account posts, such as a weather service, an emergency agency, a launch provider or a console maker. The first two are either switched off or behind a paid tier (the dates and sources are in the next section). The third you can build yourself in about 100 lines of Python, and this page is that build, with the parts that the hosted recipes from automation services (IFTTT, Zapier, an SMS vendor's Twitter connector) decide for you: which posts count, how long a text is, when it arrives, and what it costs.

The build is two API calls. TwitterAPI.io's search endpoint returns the posts a set of accounts created since your last pass, in one request, with no X account or developer app on your side. Twilio's Messages API turns a string into an SMS for $0.0083 a segment in the US (Twilio's own pricing page, fetched for this article). Between the two sits a script that remembers what it already sent, drops replies and reposts if you want it to, cuts long posts so one alert is not six texts, and can hold overnight posts for a morning digest. If you would rather have posts pushed to you in under a second instead of polling, the Twitter Stream product on this site does that over a webhook on a flat monthly plan, and we say where it fits.

Then the part no other page does: we pulled the 100 most recent posts, replies included, from each of 10 organization accounts people typically want texts from (@NWS, @FEMA, @NASA, @BBCBreaking, @CNN, @SpaceX, @PlayStation, @Xbox, @Steam, @Microsoft) in 2026 and measured how many texts a month each would generate, how many of those would be replies or reposts, how many SMS segments each post needs, when in the day they land, and how long the silences are. Every default in the script, from -filter:replies to the 300-character body cap, comes from one of those numbers.

01 — Section

Three things "Twitter to SMS" means, and which one this page builds

Tweeting by text message. Twitter was designed around SMS and the original 140-character limit was chosen to fit a text. On September 5, 2019, after Jack Dorsey's account was hijacked through the tweet-by-SMS feature, Twitter announced it was "temporarily turning off the ability to Tweet via SMS, or text message, to protect people's accounts", citing "vulnerabilities that need to be addressed by mobile carriers" (reported by Ubergizmo that day, quoting Twitter's statement). Twitter said it planned to reactivate the feature in markets that depend on SMS; we could not fetch an X help page this session that describes a current tweet-by-SMS service, so this page does not claim one exists. The X help center page for SMS features is behind a fetch block; check help.x.com directly if you are in one of the markets Twitter mentioned.

X's own text notifications when an account posts. This is the feature the old "turn on mobile notifications" and "text ON username to 40404" advice refers to. Per a Slashdot report of April 27, 2020, Twitter "turned off the ability to receive SMS messages containing the text of new tweets" in all but a few countries the week before. The X apps still offer push notifications for an account's posts, which is a different channel: they go to the app, not to a phone number, and they stop when you log out.

SMS two-factor authentication. Since March 20, 2023 text-message 2FA has been limited to Twitter Blue, now X Premium, subscribers; non-subscribers who did not switch to an authenticator app or a security key had 2FA turned off automatically (Engadget, February 18, 2023, quoting Twitter's notice; the X help page help.x.com/en/managing-your-account/two-factor-authentication states the same and notes availability varies by country and carrier for subscribers). This matters here only because it is why searches for "twitter sms" return 2FA complaints: it has nothing to do with getting alerts.

What people meanStatusWhat to do
Post a tweet by sending a textswitched off in September 2019, described as temporary; no current help page founduse the X app, or an authenticated API session (see below)
Get X's own text when an account postsswitched off in most countries in April 2020build it: this page
SMS code at loginX Premium only since March 20, 2023authenticator app or security key

The rest of this page builds the second row. If you came here to post to X from a script rather than to get texts, that is a different product on TwitterAPI.io: one /twitter/user_login_v2 login ($0.10, with a proxy you supply) returns login_cookies, and each /twitter/create_tweet_v2 call is then $0.002, billed per HTTP 200 call. The call acts as that account, so X can rate-limit, lock or suspend it; keep a human pace and expect the session to need a re-login now and then.

02 — Section

What you need: a TwitterAPI.io key and a Twilio number

TwitterAPI.io. Sign up with Google, copy the API key from the dashboard, and send it in an X-API-Key header. Reads need no X account. Tweet endpoints, including the search endpoint used here, cost 15 credits per tweet returned ($0.15 per 1,000) with a minimum of 15 credits per request, so a pass that finds nothing costs $0.00015 and a pass that finds three new posts costs $0.00045. $1 buys 100,000 credits; new accounts that pass the automatic bot check get up to $1 of free credits (at most 100,000 credits, no card, one per person or network, expiring after 31 days), which covers several weeks of five-minute polling. Accounts that have never paid are limited to 0.2 requests a second; a poll once a minute is 60x below that.

Twilio. You need an Account SID, an Auth Token and a phone number that can send SMS to your number. Twilio's US SMS pricing page lists outbound and inbound SMS at $0.0083 per message segment for long codes, toll-free numbers and short codes alike, notes that "additional carrier fees may apply" depending on the destination carrier, that US A2P 10DLC traffic is subject to registration onboarding fees passed through to you, and that a failed message carries a $0.001 processing fee. The pricing page is the source for every SMS dollar figure on this page; the per-segment price is what changes, so check it before you budget.

The Messages API. Per Twilio's Message resource documentation, sending is POST https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/Messages.json with To (E.164, e.g. +15551234567), From or MessagingServiceSid, and Body (up to 1,600 characters), authenticated with HTTP basic auth using the Account SID and Auth Token. The JSON response includes sid, status (queued or accepted at first), num_segments and, once sent, price. Twilio splits text longer than one segment automatically and bills per segment: 160 characters per segment for GSM-7 text, 70 for UCS-2 (any emoji, curly quote or other character outside the GSM-7 set switches the whole message to UCS-2). That rule is why the script caps the body, and the study below shows how much it saves.

Nothing else is required: no server, no webhook URL, no database. A laptop that stays on, a $5 VM, or a GitHub Actions schedule (5-minute granularity) can run the one-pass script from cron.

03 — Section

Step 1: find new posts with one search call per pass

The full script is at the end of the page. The X side of it is one function, fetch_new, and three decisions.

Search, not the timeline endpoint. The obvious call for "what did @NWS post" is /twitter/user/last_tweets, but it returns the account's latest 20 posts on every call, and tweet endpoints bill per tweet returned: $0.003 a page whether or not anything is new. Polled every 60 seconds that is 43,200 pages, $129.60 a month, per account. /twitter/tweet/advanced_search with a since_time: operator returns only posts created after that Unix timestamp, so an idle pass returns an empty tweets list at the $0.00015 minimum: $6.48 a month at 60 seconds, $1.30 at 5 minutes, for any number of handles that fit in one query. We used last_tweets for the study because we wanted replies and reposts counted; the script uses it only if you ask for a backfill.

One query for all accounts, replies excluded by default. from: operators join with OR, so (from:NWS OR from:FEMA OR from:NASA) -filter:replies since_time:1760000000 with queryType=Latest is one request for three accounts. Keep the query under 512 characters, roughly 25 handles; past that, split into two queries per pass. -filter:replies is the default in the script because replies were 15.3% of the posts we measured, and for @Xbox 72%: an account answering customer questions all day would otherwise text you each answer. Reposts are not returned by a from: search at all (we compared the search with the timeline endpoint on an earlier study of the same kind); they were 23.8% of the sample, and 68% of @FEMA's posts. If you do want reposts texted, poll last_tweets for that one account and pay the full page.

Overlap plus a seen set. Indexing lag means a post created at 12:00:59 can appear in the search a little after your 12:01 pass. The script therefore re-reads the last 120 seconds before its previous run (OVERLAP) and drops anything whose id is already in state["seen"], a list it trims to 2,000 entries in sms-state.json. Overlap costs nothing extra when idle (same minimum charge). New posts are sorted oldest first by createdAt, whose format is Thu Oct 16 12:34:56 +0000 2026, not ISO 8601, so the texts arrive in order.

Pages. The response is tweets[], has_next_page and next_cursor; the script follows the cursor up to MAX_PAGES (3), which is 60 posts in one pass. In the sample the busiest 5-minute window of any account held 5 posts and the busiest clock hour 14, so one page is almost always enough.

bash
# Smoke test the X side before writing any Twilio code: new posts from two accounts in the last hour.
# A pass with nothing new prints an empty tweets list and costs the $0.00015 minimum.
SINCE=$(( $(date +%s) - 3600 ))
curl -s -G "https://api.twitterapi.io/twitter/tweet/advanced_search" \
  -H "X-API-Key: $TWITTERAPI_IO_API_KEY" \
  --data-urlencode "query=(from:NWS OR from:FEMA) -filter:replies since_time:$SINCE" \
  --data-urlencode "queryType=Latest" | python3 -c '
import json, sys
b = json.load(sys.stdin)
for t in b.get("tweets") or []:
    print(t["createdAt"], "@" + t["author"]["userName"], t["url"])
print("has_next_page:", b.get("has_next_page"))'
04 — Section

Step 2: send the text through Twilio, and keep it to two segments

The Twilio side is one requests.post with basic auth and three form fields. The decisions are about the body.

Body shape. @NWS: <text> <link>. The handle first so a glance tells you who posted; the link last and never cut, because tapping it is the point of the text; the url field is the post's full x.com/<handle>/status/<id> address, about 50 characters. Newlines are replaced with spaces: a text message does not need the post's paragraph breaks and they count as characters.

Cap the body at 300 characters. Posts are long now: only 12.9% of the 1,000 posts we measured would fit in one 160-character GSM-7 segment once formatted, the average full-length body needed 3.22 segments, 36.0% contained an emoji or another character outside GSM-7 and therefore counted in 70-character UCS-2 segments, the longest needed 28 segments, and 16 posts would have needed more than 10; Twilio also caps Body at 1,600 characters, which the longest posts exceed. Cutting the text so the whole body stays under 300 characters (text first, link kept whole, an ellipsis added) brings the average to 2.58 segments and the maximum to 5. At $0.0083 a segment the cap saves about 20% on the SMS bill and makes the cost per text predictable: at most $0.0415, typically $0.0166 to $0.0249.

Errors and limits. The script raises on any non-2xx from Twilio after logging the response body, which carries Twilio's error code and message; a wrong From number, an unverified To on a trial account, and an unregistered sender are the usual first-run failures and all come back as 4xx with a readable message. Twilio's price field is empty on the create response and filled in later, so if you want to audit the bill per text, fetch the message by sid afterwards or read the usage records in the console.

Quiet hours and bursts. Two optional guards sit in run_once. QUIET_UTC = (3, 11) holds posts created between 03:00 and 10:59 UTC (23:00 to 06:59 US Eastern during daylight time) in the state file and sends them as one digest on the first pass after the window; in the sample that band held 8.9% of posts, so it costs little to wait. MAX_TEXTS_PER_PASS = 10 sends the first ten of a burst and one more text saying how many followed, which caps a bad hour at 11 texts; the busiest clock hour we saw was 14 posts from @Xbox with replies included, and 9 from @FEMA.

bash
# Smoke test the Twilio side: one text to your own phone. Prints sid, status and num_segments.
curl -s -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Messages.json" \
  -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
  --data-urlencode "To=$SMS_TO" \
  --data-urlencode "From=$TWILIO_FROM" \
  --data-urlencode "Body=@NWS: test alert from the twitter_to_sms script https://x.com/NWS" \
  | python3 -c 'import json,sys; m=json.load(sys.stdin); print(m.get("sid"), m.get("status"), "segments:", m.get("num_segments"), m.get("message") or "")'
05 — Section

What the texts would look like: 1,000 posts from 10 organization accounts

To size the alerts we pulled the 100 most recent posts, replies included (includeReplies=true), from each of 10 organization accounts people typically want texts from: a weather and an emergency agency (@NWS, @FEMA), a space agency and a launch provider (@NASA, @SpaceX), two news brands (@BBCBreaking, @CNN), three gaming platforms (@PlayStation, @Xbox, @Steam) and @Microsoft. Each handle was confirmed first with /twitter/user/info (@Apple was in the plan and was dropped because its profile reports 0 posts). 50 timeline requests plus 11 profile lookups, 1,000 posts, about $0.15 in API reads.

Volume varies 200-fold, and most of it is not what you want texted. Extrapolated from the days each sample spans, the accounts post between 4 (@Steam, whose 100 posts reach back 702 days) and 970 (@Xbox, 3.1 days) times a month. But 39.1% of all posts were replies (15.3%) or reposts (23.8%), and the share is lopsided: @Xbox is 72% replies, @FEMA 68% reposts, @NWS 52% reposts, @Microsoft 51% reposts, @NASA 27% replies and 30% reposts. @CNN (98% original) and @BBCBreaking (96%) are the opposite. Once replies and reposts are removed, the texted volume is 4 to 586 a month per account and 1,356 a month for all ten, instead of 2,840.

Length. 36.0% of the bodies contain a character outside GSM-7 (emoji in most cases), which drops the segment size from 160 to 70 characters; @NASA, @Microsoft, @FEMA and @PlayStation are over half UCS-2, @BBCBreaking 5%. 43.4% of posts carry a photo or video, which the text can only link to.

AccountPosts a month (all)Replies / repostsTexts a month (originals + quotes)SMS a month at $0.0083/segment
@Xbox97072% / 19%87$2.09
@CNN5981% / 1%586$11.12
@FEMA36510% / 68%80$2.24
@NWS2741% / 52%129$2.28
@SpaceX17713% / 8%140$2.49
@PlayStation1613% / 8%143$3.51
@NASA14927% / 30%64$1.73
@BBCBreaking1214% / 0%116$1.93
@Microsoft2117% / 51%7$0.18
@Steam45% / 1%4$0.08
All ten2,84015.3% / 23.8%1,356$27.65

SMS cost is texts a month multiplied by the measured segments per text for that account's original posts with the 300-character body cap (2.0 for @BBCBreaking up to 3.36 for @FEMA) and Twilio's US outbound price. Texting every post including replies and reposts would cost $55.98 a month for the ten; @Xbox alone would be $14.73.

Horizontal stacked bar chart of posts per 30 days for 10 organization accounts, blue for original posts and quotes that would be texted and gray for replies and reposts that the script filters out. Xbox 970 posts a month of which 87 texted, CNN 598 of which 586, FEMA 365 of which 80, NWS 274 of which 129, SpaceX 177 of which 140, PlayStation 161 of which 143, NASA 149 of which 64, BBCBreaking 121 of which 116, Microsoft 21 of which 7, Steam 4 of which 4. Right margin lists the SMS cost of the texted share per month at $0.0083 a segment: $2.09, $11.12, $2.24, $2.28, $2.49, $3.51, $1.73, $1.93, $0.18, $0.08.
Posts per 30 days per account, 100 most recent posts each, collected in 2026. Blue is what a from: search with -filter:replies would text; gray is replies and reposts. Right: the SMS bill for the blue share.
06 — Section

When the texts arrive, and how bursty they are

Afternoon in the US, quiet overnight. Across all 1,000 posts, 27.8% were created between 17:00 and 19:59 UTC (1 pm to 4 pm US Eastern) and the single busiest hour, 17:00 UTC, held 110 posts. The band from 03:00 to 10:59 UTC, which is 11 pm to 7 am Eastern, held 89 posts, 8.9%. Two accounts are the exception: 37% of @BBCBreaking's posts and 28% of @SpaceX's landed in that band, because one is in London and the other launches at night. If your subscriber is in the US, a quiet-hours digest loses little; if the account you follow is in another time zone, shift the window.

Gaps are minutes for the busy accounts and days for the quiet ones. The median gap between consecutive posts, replies included, was 7 minutes for @Xbox, 26 for @SpaceX, 29 for @FEMA, 34 for @CNN and 48 for @NWS, against 1.6 hours for @NASA, 1.9 hours for @PlayStation, 3 hours for @Microsoft, 3.5 hours for @BBCBreaking and 6 days for @Steam. The longest silence in each sample ran from 8.5 hours (@CNN) through about 23 hours (@NWS) to 27 and 28 days (@Microsoft, @Steam). For the script this means a 60-second poll is already far more frequent than anything happens: 97.9% to 100% of 60-second windows and 91.5% to 100% of 5-minute windows held no post at all, and fewer than 2% of 5-minute windows held more than one.

Bursts exist but are small. The most posts in one clock hour were 14 (@Xbox, replies), 9 (@FEMA, a repost run), 8 (@Microsoft) and 7 (@CNN); in any 5-minute window the maximum was 5 (@FEMA) and otherwise 3 or fewer. The MAX_TEXTS_PER_PASS guard exists for the day an account live-posts an event, not for a normal day.

AccountMedian gapLongest gapPeak posts in one hourPosts 03:00-10:59 UTC
@Xbox7.2 min18.1 h140%
@SpaceX26.0 min2.4 d628%
@FEMA29.1 min21.0 h90%
@CNN33.9 min8.5 h711%
@NWS48.1 min23.1 h55%
@NASA1.6 h1.9 d64%
@PlayStation1.9 h1.7 d40%
@Microsoft3.0 h27.0 d82%
@BBCBreaking3.5 h1.7 d237%
@Steam6.0 d28.0 d22%
Bar chart of 1,000 posts from 10 organization accounts by hour of day in UTC. A shaded band from 03:00 to 10:59 UTC, labeled 23:00 to 06:59 US Eastern, holds 8.9% of posts with hourly counts of 16, 2, 3, 1, 14, 10, 18 and 25. Counts rise from 18 at 11:00 to a peak of 110 at 17:00; the three orange bars 17:00, 18:00 and 19:00 UTC hold 27.8% of all posts with 110, 91 and 77. Evening hours 20:00 to 23:00 hold 66, 91, 56 and 30.
Posts by hour of day (UTC), 10 organization accounts, 1,000 posts collected in 2026. Orange marks the three busiest hours; the shaded band is overnight for a US Eastern reader.
07 — Section

What it costs a month: 1, 5, 10 and 20 accounts

Two bills: the polling floor on TwitterAPI.io, which does not depend on how many accounts fit in the query, and the SMS bill, which is texts multiplied by segments multiplied by Twilio's $0.0083. The rows below use the measured accounts: @NWS alone, then @NWS, @FEMA, @NASA, @SpaceX and @PlayStation, then all ten. The 20-account row is an assumption, not a measurement: it doubles the ten-account mix.

AccountsTexts a monthSMS at $0.0083/segmentPolling floor at 60 s / 5 minTotal at 60 s / 5 min
1 (@NWS)129$2.28$6.48 / $1.30 (+ $0.02 for returned posts)$8.78 / $3.60
5 (@NWS, @FEMA, @NASA, @SpaceX, @PlayStation)556$12.25$6.48 / $1.30 (+ $0.08)$18.81 / $13.63
10 (all measured)1,356$27.65$6.48 / $1.30 (+ $0.20)$34.33 / $29.15
20 (assumed: the ten-account mix twice)about 2,712about $55.30$6.48 / $1.30 (+ about $0.41)about $62.19 / $57.01

Where the money goes. For one quiet account the polling floor is the bill, and dropping from 60 seconds to 5 minutes cuts the total by more than half. From five accounts up the SMS side dominates, and the levers are the ones the script already has: -filter:replies (which turned 2,840 posts into 1,356), the 300-character cap (about 20% off the segment count), and the quiet-hours digest. Polling more often than once a minute buys nothing: the busiest account's median gap was 7 minutes.

Against the official X API. The X API pay-per-usage rate card (docs.x.com) prices each post read at $0.005 and each user read at $0.010, with pay-per-usage capped at 3 million post reads a month and Enterprise above; new developer accounts get $20 of credits on a first card plus a match of up to $50. Per 1,000 posts read that is $5 against TwitterAPI.io's $0.15, about 33x. For this use the difference is mostly in the floor: a since_id search on the official API that returns nothing is not billed as a post read, but you need the paid developer account and app before the first call, and every post returned is $0.005 instead of $0.00015, so the ten-account feed's 1,356 posts cost $6.78 a month to read there and $0.20 here. Both are small next to the SMS bill; the gate is the developer account.

Against pushing instead of polling. The Twitter Stream product on this site watches a fixed list of accounts and delivers their posts over a webhook (an HTTP POST to your URL) or a WebSocket, with the stated latency of 50% of posts under 500 ms and 80% under one second, on flat monthly plans priced per monitored account rather than per call. It is the right tool when seconds matter (a launch, a market-moving account) or when you watch hundreds of accounts; the script on this page is the right tool for a handful of accounts where a minute or five is fine. Either way, the Twilio side is unchanged: your webhook receiver calls send_sms instead of the polling loop. The push payload is described on the webhooks guide linked below. Plans with bonus credits for the polling route are on the pricing page.

08 — Section

Troubleshooting a Twitter to SMS alert

No text arrives but the script prints new posts. Twilio accepted the message (status: queued) but could not deliver it. Fetch the message by its sid from the Messages resource and read status and error_code; on a trial account To must be a verified number, and a US long code that is not registered for A2P 10DLC can be filtered by carriers. Twilio's pricing page notes the registration fees; the console's messaging logs show the reason.

Texts arrive as several messages. The body exceeded one segment and the phone did not reassemble it, or the body had an emoji and switched to 70-character segments. Keep BODY_CAP at 300 or lower it to 160 for GSM-7-only posts; num_segments in the create response tells you what you were billed for.

The same post texted twice. The state file was not written (permissions, or two copies of the script running), or the second copy had its own state. Run one instance per state file and make sure the process can write sms-state.json where it runs.

Posts missing that the account clearly made. Reposts are not returned by a from: search, and replies are dropped by -filter:replies. Set INCLUDE_REPLIES = True for replies; for reposts poll /twitter/user/last_tweets for that account and pay the $0.003 page. Also confirm the handle with /twitter/user/info (an unknown handle returns HTTP 200 with status: "error" and msg: "user not found", never a 404), because organizations rename accounts.

HTTP 400 from TwitterAPI.io. A parameter is missing or malformed; the response body names it. The usual cause is a query over 512 characters after adding too many from: handles.

HTTP 429 from TwitterAPI.io. Accounts that have never paid are limited to 0.2 requests a second. One query per pass never hits that; if you split into several queries, space them a few seconds apart.

Texts at 4 am. Set QUIET_UTC to the band you want held and the script sends one digest afterwards. Remember the band is in UTC and that two of the ten accounts we measured post 28% to 37% of the time in US night hours.

Twilio returns 429. You are sending faster than your account allows; the script already sends texts one at a time with a one-second pause, which is far below any limit for a feed whose busiest 5-minute window held 5 posts. If it still happens, raise the pause or lower MAX_TEXTS_PER_PASS.

09 — Section

How we collected this

Question: if every post from an organization account became a text message, how many texts a month would arrive, how many of them would be replies or reposts, how many SMS segments would each need, when in the day would they land, how long are the gaps, and what would the Twilio bill be per account.

Accounts: 10 public organization accounts: @NWS, @FEMA, @NASA, @BBCBreaking, @CNN, @SpaceX, @PlayStation, @Xbox, @Steam and @Microsoft, each confirmed with /twitter/user/info first. @Apple was planned and dropped because its profile reports 0 posts.

Method: /twitter/user/last_tweets with includeReplies=true, 5 pages of 20 per account, newest first: 50 timeline requests plus 11 profile lookups, 1,000 posts, collected in 2026 through a rate-limited, read-only client. For each account we measured the span from oldest to newest post, posts per day and per 30 days, the share of replies (isReply), reposts (retweeted_tweet), quotes (quoted_tweet) and originals, gaps between consecutive posts, posts per UTC clock hour and per 5-minute window, the share of 60-second and 300-second windows with no post, the presence of media, and the number of SMS segments the formatted body would need under Twilio's GSM-7 (160/153) and UCS-2 (70/67) rules, both at full length and with the 300-character cap the script applies. SMS cost is texts a month multiplied by measured segments per text multiplied by $0.0083, the US outbound price on Twilio's pricing page at the time of writing.

What the sample cannot show: 100 posts per account is a snapshot, and for @Steam and @Microsoft it reaches back months, so their posts per month are long-run averages that hide launch weeks; for @Xbox and @CNN it covers 3 to 5 days, which may be busier or quieter than usual. Ten organization accounts are not individuals, who post differently. Twilio carrier fees and registration fees are not in the SMS figures. The X API and X help facts are from the sources listed, not from testing the official API.

Aggregates only. The accounts named are organizations; no individual's posts were analyzed and the authors of reposted or replied-to posts were not examined. The analysis script, results and the gzip-compressed raw pages for these organization accounts are kept with the study.

python
"""twitter_to_sms.py - get a text message when chosen X (Twitter) accounts post.

    pip install requests
    export TWITTERAPI_IO_API_KEY=...                       # twitterapi.io dashboard
    export TWILIO_ACCOUNT_SID=AC...  TWILIO_AUTH_TOKEN=...  # twilio console
    export TWILIO_FROM=+15550000000  SMS_TO=+15551234567    # your Twilio number, your phone
    python twitter_to_sms.py               # one pass: run it from cron every 1-5 minutes
    python twitter_to_sms.py --loop 300    # or keep it running and poll every 300 s

Cost: one advanced_search call per pass. A pass that finds nothing costs the $0.00015 minimum
(43,200 passes a month at 60 s = $6.48; 8,640 at 5 min = $1.30), plus $0.00015 per new post
returned, plus Twilio's US price of $0.0083 per SMS segment for every text sent.
"""
import argparse
import json
import os
import sys
import time
from datetime import datetime, timezone

import requests

API = "https://api.twitterapi.io"
X_KEY = os.environ["TWITTERAPI_IO_API_KEY"]
TW_SID = os.environ["TWILIO_ACCOUNT_SID"]
TW_TOKEN = os.environ["TWILIO_AUTH_TOKEN"]
TW_FROM = os.environ["TWILIO_FROM"]
SMS_TO = os.environ["SMS_TO"]

WATCH = ["NWS", "FEMA", "NASA"]   # handles to get texts for; keep the query under 512 characters (about 25 handles)
INCLUDE_REPLIES = False           # replies were 15.3% of our sample (72% for @Xbox); reposts are not returned by a from: search
STATE_FILE = "sms-state.json"
OVERLAP = 120                     # seconds re-read on every pass; the seen-ids set removes the duplicates
BODY_CAP = 300                    # characters per text: 2 GSM-7 segments, at most 5 if the post has emoji (UCS-2)
QUIET_UTC = None                  # e.g. (3, 11): hold posts created 03:00-10:59 UTC and send one digest afterwards
MAX_TEXTS_PER_PASS = 10           # a burst guard: the busiest clock hour in our sample held 14 posts (@Xbox, replies)
MAX_PAGES = 3                     # 60 posts per pass is plenty; the busiest 5-minute window we saw held 5


def load_state():
    if os.path.exists(STATE_FILE):
        with open(STATE_FILE) as f:
            return json.load(f)
    return {"since_time": int(time.time()) - 3600, "seen": [], "held": []}   # first run: the last hour


def save_state(state):
    state["seen"] = state["seen"][-2000:]
    with open(STATE_FILE, "w") as f:
        json.dump(state, f)


def build_query(since_time):
    q = "(" + " OR ".join(f"from:{h}" for h in WATCH) + ")"
    if not INCLUDE_REPLIES:
        q += " -filter:replies"
    return f"{q} since_time:{since_time}"


def fetch_new(since_time):
    """Only posts newer than since_time come back, so an idle pass returns an empty list."""
    tweets, cursor = [], None
    for _ in range(MAX_PAGES):
        params = {"query": build_query(since_time), "queryType": "Latest"}
        if cursor:
            params["cursor"] = cursor
        r = requests.get(f"{API}/twitter/tweet/advanced_search",
                         headers={"X-API-Key": X_KEY}, params=params, timeout=30)
        if r.status_code == 400:
            sys.exit(f"twitterapi.io 400: {r.text}")   # the body names the bad parameter; usually a query over 512 characters
        r.raise_for_status()
        body = r.json()
        page = body.get("tweets") or []
        tweets.extend(page)
        if not page or not body.get("has_next_page") or not body.get("next_cursor"):
            break
        cursor = body["next_cursor"]
    return tweets


def parse_time(s):
    # createdAt looks like "Thu Oct 16 12:34:56 +0000 2026", not ISO 8601
    return datetime.strptime(s, "%a %b %d %H:%M:%S %z %Y")


def sms_body(t):
    """'@handle: text link'. The link is never cut; the text is, so one alert stays at 2 segments (5 with emoji)."""
    a = t.get("author") or {}
    text = (t.get("text") or "").replace("\n", " ")
    url = t.get("url") or t.get("twitterUrl") or ""
    head = f"@{a.get('userName', '')}: "
    room = BODY_CAP - len(head) - len(url) - 1
    if len(text) > room:
        text = text[:room - 3].rstrip() + "..."
    return f"{head}{text} {url}"


def send_sms(body):
    """Twilio Message resource: POST .../Accounts/{sid}/Messages.json with To, From, Body (basic auth)."""
    r = requests.post(f"https://api.twilio.com/2010-04-01/Accounts/{TW_SID}/Messages.json",
                      auth=(TW_SID, TW_TOKEN),
                      data={"To": SMS_TO, "From": TW_FROM, "Body": body[:1600]}, timeout=15)
    if r.status_code >= 400:
        sys.exit(f"Twilio {r.status_code}: {r.text}")      # body carries Twilio's error code and message
    m = r.json()
    print(f"sent {m.get('sid')} status={m.get('status')} segments={m.get('num_segments')}", file=sys.stderr)
    time.sleep(1)


def in_quiet_hours(now_utc):
    return bool(QUIET_UTC) and QUIET_UTC[0] <= now_utc.hour < QUIET_UTC[1]


def run_once(state):
    started = int(time.time())
    now = datetime.now(timezone.utc)
    seen = set(state["seen"])
    new = [t for t in fetch_new(state["since_time"] - OVERLAP) if t["id"] not in seen]
    new.sort(key=lambda t: parse_time(t["createdAt"]))      # oldest first, so the texts read in order
    bodies = [sms_body(t) for t in new]
    state["seen"].extend(t["id"] for t in new)

    if in_quiet_hours(now):
        state["held"].extend(bodies)                           # hold overnight, text one digest in the morning
        bodies = []
    elif state.get("held"):
        held = state["held"]
        digest = f"{len(held)} posts while you were away: " + " | ".join(held)
        send_sms(digest[:1600])                                 # Twilio's Body limit
        state["held"] = []

    for body in bodies[:MAX_TEXTS_PER_PASS]:
        send_sms(body)
    if len(bodies) > MAX_TEXTS_PER_PASS:
        send_sms(f"...and {len(bodies) - MAX_TEXTS_PER_PASS} more posts from {', '.join('@' + h for h in WATCH)}")

    state["since_time"] = started
    save_state(state)
    print(f"{len(new)} new post(s), {min(len(bodies), MAX_TEXTS_PER_PASS)} text(s) sent", file=sys.stderr)


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--loop", type=int, help="poll every N seconds instead of exiting after one pass")
    args = ap.parse_args()
    state = load_state()
    while True:
        run_once(state)
        if not args.loop:
            break
        time.sleep(args.loop)
10 — Questions

Questions readers ask

Can I still tweet by sending a text message to 40404?

Not as a general feature. Twitter announced on September 5, 2019 that it was temporarily turning off the ability to tweet via SMS after Jack Dorsey's account was hijacked through it, and in April 2020 it turned off receiving tweets as texts in most countries. Twitter said it would keep SMS in markets that depend on it; we could not fetch an X help page this session that describes a current service, so check help.x.com for your country. To post from a script, use the X app or an authenticated API session instead.

How do I get a text when a specific Twitter (X) account posts?

Run a script that polls the TwitterAPI.io search endpoint with a query such as (from:NWS OR from:FEMA) -filter:replies since_time:<unix> every one to five minutes, keeps the IDs it already sent in a small file, and sends each new post to your phone through Twilio's Messages API (POST .../Accounts/{sid}/Messages.json with To, From and Body). The complete Python script is on this page; it needs an API key and a Twilio number, no X account.

How much does a Twitter to SMS alert cost per month?

Two parts. Polling: one search call per pass at the $0.00015 minimum when nothing is new, so $6.48 a month at 60 seconds or $1.30 at 5 minutes, for any number of handles that fit in one query. SMS: Twilio's US price is $0.0083 per segment, and with the 300-character cap a text averages 2.58 segments. For one account like @NWS (about 129 original posts a month) that is about $2.28 in SMS, $8.78 in total at 60-second polling; for the ten accounts we measured, about $34.33.

Why does one alert arrive as two or three text messages?

Because the body is longer than one SMS segment: 160 characters for plain GSM-7 text, 70 if the post contains an emoji or another character outside the GSM-7 set. Only 12.9% of the 1,000 posts we measured fit one segment at full length. The script cuts the text so the body stays under 300 characters and keeps the link, which caps an alert at 2 segments (5 with emoji); Twilio bills per segment and reports the count in num_segments.

Will I get texted every reply and retweet too?

Not by default. A from: search does not return reposts, and the script adds -filter:replies, so only original posts and quotes are texted. That matters: 39.1% of the posts we measured were replies or reposts, 72% of @Xbox's posts were replies and 68% of @FEMA's were reposts. Set INCLUDE_REPLIES = True for replies; for reposts poll /twitter/user/last_tweets for that account and accept the $0.003 full-page price.

Is SMS two-factor authentication on X free?

No. Since March 20, 2023 text-message 2FA has been available only to X Premium (formerly Twitter Blue) subscribers; everyone else uses an authenticator app or a security key. This is unrelated to getting post alerts by text, which does not involve your X login at all.

Can I get the posts pushed to me instead of polling every minute?

Yes. The Twitter Stream product on this site watches a fixed list of accounts and delivers their posts to a webhook URL or over a WebSocket, with a stated latency of 50% of posts under 500 ms and 80% under one second, on flat monthly plans. Your receiver then calls the same Twilio function. Use it when seconds matter or you watch many accounts; the polling script is enough for a handful of accounts where a minute or five is fine.

Do I need an X developer account for this?

No. Reads on TwitterAPI.io need only an API key in the X-API-Key header. The official X API alternative requires a paid developer account and app and charges $0.005 per post read (docs.x.com rate card), about 33x the $0.15 per 1,000 posts here; for an alert feed the bigger difference is the account you have to set up before the first call.

11 — Further reading

Continue

Sources & further reading
More from this series
Build it

Stop reading. Start building.

Starter credits cover real testing on real data. Google sign-in, no card, no application queue.

Get an API key