Abstract / Overview
This tutorial demonstrates a free and reliable method for publishing daily tweets from a Google Sheet using Make.com and Buffer. The spreadsheet acts as your content calendar. Make reads rows, schedules posts in Buffer, and marks status back in the sheet. No servers. No paid APIs.
Assumption: Makeâs Free plan offers 1,000 operations per month with a 15-minute minimum schedule; Bufferâs Free plan allows up to 10 scheduled posts per connected channel. These are sufficient for light personal or small business use.
Conceptual Background
Direct X (Twitter) posting from Make was removed in April 2025. Use Buffer as the publish layer. Bufferâs Make app can create and schedule updates to X.
The free stack: Google Sheets (data), Make (orchestration), Buffer (publisher). Sheets holds text, media URLs, times, and status. Make polls or listens for changes and calls Bufferâs âCreate a status update.â Buffer pushes to X at the scheduled time.
Free-tier constraints to plan for:
Make Free: 1,000 operations per month; 15-minute minimum schedule cadence. Webhooks can trigger instantly, reducing polling overhead.
Buffer Free: 10 scheduled posts per channel at any time; up to 3 channels total. Daily posting limits on X still apply.
Step-by-Step Walkthrough
1) Plan your sheet schema
Create a Google Sheet X Scheduler
. Define a header row:
Active
(TRUE/FALSE)
Profile
(human label, e.g., @yourhandle
)
BufferProfileId
(from Buffer profile list)
Text
(tweet body, 280 chars for free X accounts)
ImageURL
(optional; direct URL to PNG/JPG/GIF)
ScheduledDate
(YYYY-MM-DD)
ScheduledTime
(HH:mm, 24-hour)
Timezone
(IANA, e.g., America/New_York
)
Status
(QUEUED
, POSTED
, ERROR
)
PostId
(Buffer returned ID)
Notes
(error messages)
Tips:
2) Connect Buffer and find your X profile
Create a free Buffer account and connect your X profile. The free plan supports up to three channels and limits scheduled posts to 10 per channel at a time.
In Make, add the Buffer app and authenticate. Confirm the module âCreate a status updateâ supports âpost at a scheduled date and time.â
In the Buffer module, list profiles and capture the chosen X profile ID. Paste that into BufferProfileId
for each row.
3) Build the scenario in Make (polling version, Free plan friendly)
Trigger: Google Sheets â Search Rows. Filter Active = TRUE AND Status IS EMPTY
. Limit to a small batch (5â10).
Router with two paths:
Path A â Buffer: âCreate a status updateâ
Profiles: map BufferProfileId
.
Text: map Text
.
Publication: âpost at a scheduled date and time.â Compute ISO 8601 using ScheduledDate
, ScheduledTime
, and Timezone
.
Media: map ImageURL
when present.
Update Sheet: set Status = "QUEUED"
and write the Buffer update_id
into PostId
.
Scheduler: every 15 minutes to align with Free plan.
Why âSearch Rows,â not âWatch Rowsâ? âWatchâ is event-like but still polling; âSearchâ plus a tight filter avoids scanning old content and reduces operations. Community guidance confirms Watch isnât instant. Webhooks can replace polling if you prefer.
4) Optional: instant triggering with a webhook
Reduce ops by firing a Make Custom Webhook when a row is added or edited in Sheets via Apps Script. Webhooks trigger runs immediately and avoid the 15-minute cadence.
5) Handle images and formatting
Use full HTTPS links in ImageURL
. Keep one image per update on the free flow for simplicity. Use line breaks cautiously; preview in Bufferâs composer if in doubt.
6) Test end-to-end
Add two rows: one due 30 minutes from now, one due tomorrow.
Run the scenario once. Confirm Buffer shows one scheduled post.
On publish, verify the tweet on X and mark Status = "POSTED"
via a follow-up path that checks Bufferâs status endpoint or by manual confirmation on the free stack.
7) Operate day-to-day
Keep your sheet filled with the next 10 posts to stay under Bufferâs free queue limit. When one publishes, a new slot opens.
Use one scenario only to stay within Makeâs Free plan allowances.
Code / JSON Snippets
A. Minimal ISO 8601 datetime assembly in Make
Use a âSet multiple variablesâ module before Buffer:
{{ formatDate(
parseDate(concat(ScheduledDate; " "; ScheduledTime); "YYYY-MM-DD HH:mm"; Timezone);
"YYYY-MM-DDTHH:mm:ssZ";
"UTC"
) }}
Map the result to Bufferâs âDate scheduled.â
B. Google Sheets seed data (CSV)
Active,Profile,BufferProfileId,Text,ImageURL,ScheduledDate,ScheduledTime,Timezone,Status,PostId,Notes
TRUE,@yourhandle,PROF_123,"New blog post is live: https://example.com/blog-123",,2025-09-01,10:00,America/New_York,,,
TRUE,@yourhandle,PROF_123,"Tip #7: Use keyboard shortcuts to ship faster.",https://example.com/img/tip7.png,2025-09-02,10:00,America/New_York,,,
C. Apps Script to trigger a Make webhook on new or edited rows (optional)
Paste into Extensions â Apps Script and replace YOUR_WEBHOOK_URL
.
function onEdit(e) {
const sheet = e.source.getActiveSheet();
if (sheet.getName() !== 'X Scheduler') return;
const row = e.range.getRow();
if (row === 1) return; // skip header
const data = sheet.getRange(row,1,1,sheet.getLastColumn()).getValues()[0];
const headers = sheet.getRange(1,1,1,sheet.getLastColumn()).getValues()[0];
const payload = {};
headers.forEach((h, i) => payload[h] = data[i]);
UrlFetchApp.fetch('YOUR_WEBHOOK_URL', {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
}
D. Sample workflow JSON code (Make scenario blueprint)
This blueprint searches due rows and schedules them in Buffer.
{
"name": "Sheet â Buffer â X Scheduler (Free)",
"version": 3,
"modules": [
{
"id": "1",
"name": "Search due rows",
"type": "google-sheets",
"func": "searchRows",
"params": {
"connectionId": "conn_gs_1",
"spreadsheetId": "YOUR_SPREADSHEET_ID",
"sheetName": "X Scheduler",
"query": "Active=TRUE AND Status IS EMPTY",
"limit": 10,
"considerHeaders": true
}
},
{ "id": "2", "name": "Router", "type": "router" },
{
"id": "3",
"name": "Filter: due †20 min",
"type": "flow",
"func": "filter",
"params": {
"condition": "{{ formatDate(parseDate(concat(ScheduledDate; \" \"; ScheduledTime); \"YYYY-MM-DD HH:mm\"; Timezone); \"X\"; \"UTC\") <= (formatDate(now; \"X\"; \"UTC\") + 1200) }}"
}
},
{
"id": "4",
"name": "Create scheduled update",
"type": "buffer",
"func": "createStatus",
"params": {
"connectionId": "conn_buffer_1",
"profiles": ["{{ BufferProfileId }}"],
"text": "{{ Text }}",
"publication": "schedule",
"dateScheduled": "{{ formatDate(parseDate(concat(ScheduledDate; \" \"; ScheduledTime); \"YYYY-MM-DD HH:mm\"; Timezone); \"YYYY-MM-DDTHH:mm:ssZ\"; \"UTC\") }}",
"media": [ { "link": "{{ ImageURL }}" } ]
}
},
{
"id": "5",
"name": "Mark QUEUED",
"type": "google-sheets",
"func": "updateRow",
"params": {
"connectionId": "conn_gs_1",
"spreadsheetId": "YOUR_SPREADSHEET_ID",
"sheetName": "X Scheduler",
"rowNumber": "{{ rowNumber }}",
"values": { "Status": "QUEUED", "PostId": "{{ bundle.response.id }}", "Notes": "" }
}
}
],
"links": [
{ "from_module": "1", "to_module": "2" },
{ "from_module": "2", "to_module": "3" },
{ "from_module": "3", "to_module": "4" },
{ "from_module": "4", "to_module": "5" }
],
"schedule": { "type": "interval", "interval": 15 }
}
E. Optional HTTP fallback (Buffer API)
Use Makeâs HTTP module if the Buffer app is unavailable. You must provide a Buffer access token and profile ID.
POST https://api.bufferapp.com/1/updates/create.json
Authorization: Bearer YOUR_BUFFER_ACCESS_TOKEN
Content-Type: application/x-www-form-urlencoded
profile_ids[]=YOUR_BUFFER_PROFILE_ID&
text={{Text}}&
scheduled_at={{formatDate(parseDate(concat(ScheduledDate; " "; ScheduledTime); "YYYY-MM-DD HH:mm"; Timezone); "YYYY-MM-DDTHH:mm:ssZ"; "UTC")}}&
media[photo]={{ImageURL}}
Map form fields in Makeâs HTTP module accordingly.
Use Cases / Scenarios
Personal daily tips, quotes, or link shares.
Small business promotions with one queue per product line.
Community managers curating industry news from a research tab in the same sheet.
Creators seeding evergreen threads by splitting long content into multiple rows.
Limitations / Considerations
Buffer Free limits you to 10 scheduled posts per channel at any time. Keep the sheet queue near 10 and refill as slots free up.
X daily posting limits still apply when publishing via Buffer. Plan your cadence to stay under platform caps.
Make Free has a 15-minute minimum schedule. If you need tighter timing, switch to a webhook trigger or a paid Make plan.
Media hosting must be stable and publicly accessible if you pass image URLs to Buffer.
Threads: free workflows can post single messages easily. Multi-tweet threads are supported in Buffer, but manage them thoughtfully to stay within free plan constraints.
Fixes (common pitfalls with solutions and troubleshooting tips)
Nothing appears in Buffer: Verify BufferProfileId
and that the profile is connected. Re-authenticate the Buffer app in Make.
Row never queues: Ensure Active=TRUE
, Status
empty, and scheduled time within your âdue windowâ filter.
Time offsets: Check Timezone
. Use IANA names. Confirm your ScheduledDate
and ScheduledTime
format and that you convert to ISO 8601 before sending.
Duplicate queuing: After a successful Buffer call, write Status="QUEUED"
and the PostId
. Always filter out non-empty Status
.
Over-consuming operations: Reduce schedule frequency or switch to a webhook. Batch search rows with limit
and filter upstream.
Image not attached: Provide a direct link to an image file. Avoid redirects or HTML pages.
Conclusion
You can automate daily tweets free with a spreadsheet front end, Makeâs visual workflows, and Bufferâs publisher. The pattern is simple, durable, and transparent. It respects current X API constraints by bypassing direct API calls while staying within free-tier limits on Make and Buffer.
Diagram
![diagram]()
Budget calculation
Let:
P
= posts scheduled per month.
r
= Make operations per post. Approximate one search (1) + one Buffer create (1) + one row update (1) = r â 3
.
Poll overhead per run = 1 operation. Runs per month on a 15-minute schedule â 96/day * 30 = 2,880
operations even with no posts. This exceeds Free plan. Use a webhook or reduce frequency.
Two workable free strategies:
Webhook strategy: 3 ops per post only. Ops â 3P
. With P = 250
, Ops â 750
(fits in 1,000).
Sparse polling: Run every 6 hours. Poll ops â 4/day * 30 = 120
. Add 3P
. With P = 200
, total â 120 + 600 = 720
.
Buffer Free: max 10 scheduled posts per channel simultaneously. Maintain a rolling queue of 7â10 items so slots refill as posts publish.
Future enhancements
Add a âQueue healthâ sheet that counts open Buffer slots and warns when below 3.
Add UTM tagging and bitlink shortening with Makeâs tools before scheduling.
Add a second path that posts images and alt text, with validation for missing alt text.
Add a manual âPublish nowâ checkbox that bypasses scheduling for urgent posts.
Swap Buffer for another scheduler if your team outgrows the free limits.