
ไม่ชอบบันทึกรายรับรายจ่าย ทำยังไงจะอัตโนมัติ
การบันทึกรายรับรายจ่ายจริง ๆ เป็นสิ่งที่ควรทำ แต่ก็ไม่ขยันจะเพิ่มข้อมูล เลยนึกได้ว่ามันมีทางอยู่นี่หว่าผมมีแอปติดตามบันทึกรายรับรายจ่ายที่ชอบมากโดยส่วนตัว ถ้าใครกำลังหาแอปแนวนี้ผมก็จะแนะนำตัวนี้ตลอด ซึ่งคือ Cashew เป็นแอปที่เขียนโดยใช้ Material You สวยงามและฟีเจอร์ครบครันที่สุดเท่าที่ผมเคยใช้มา แถมยังฟรีและโอเพนซอร์ซอีก แต่บล็อกนี้ผมไม่ได้จะมาแนะนำแอปหรอก ตัวแอปมันดีของมันอยู่แล้ว ปัญหาคือผมขี้เกียจเพิ่มข้อมูลเองมากกว่า หลังจากเริ่มใช้แอปมาสักพักกลายเป็นว่ามีช่วงที่ขาดไปกว้างมากจนมีครั้งนึงต้องนั่งกดขอ Statement จากธนาคารมาเพิ่มเอาเอง 5555 (ซึ่งจริง ๆ ก็ไม่ได้แย่ แต่อ่าน pdf ให้เป้ะนี่ไม่ค่อนสนุกเท่าไหร่)
จากข้อมูล statement ที่เป็นไฟล์ PDF รีเควสจากในแอป ผมก็ได้พอทราบแล้วว่าธนาคารที่ให้รายละเอียดดีที่สุดคือกสิกรไทย ทั้งชื่อผู้รับและชื่อร้านค้า แต่ยังไม่ดีพอเพราะต้องมานั่งสกัดข้อมูลเองนี่แหละ AI ก็ช่วยได้เยอะแต่ถ้าพลาดคือหายากสุด ๆ
แต่แล้ววันหนึ่ง (เมื่ออาทิตย์ที่แล้ว) ก็นึกได้ว่าเราสามารถใช้ Cloudflare Email Routing + Email Worker เพื่อรับอีเมลจากธนาคาร แล้วสกัดข้อมูลไว้ทำเป็นรายรับรายจ่ายได้
วิธีการ
ก่อนที่จะทำอะไร โดเมนที่ใช้ต้องเป็นโดเมนที่ใช้ Cloudflare name server ก่อน โดยส่วนตัวแนะนำให้ใช้ Cloudfalre ถึงคุณจะซื้อโดเมนจากใครก็เถอะ
- สร้าง Worker ใหม่ที่เป็น email worker ซึ่งทำได้จากการใช้ cli หรือในแดชบอร์ดเลยก็ได้ แต่ในที่นี้ผมจะใช้ CLI จะได้จัดการ Source control ง่าย ๆ และใช้ Wrangler เริ่มต้นด้วยการ setup ตามเรื่องตามราว
pnpx wrangler login # ถ้ายังไม่ทำ
pnpm create cloudflare
# หลังจากนั้นก็เลือกการตั้งค่าตามใจชอบ ในบล็อกนี้ใช้ TypeScript
- เพิ่ม D1 สำหรับเก็บข้อมูล ใจจริงผมอยากใช้ Google Sheet api มากแต่ใน cloudflare worker ด้วยความที่เป็น edge เลยใช้บางฟังก์ชันของ Node.js ไม่ได้ (เช่นอัลกอริทึมเข้ารหัสหลาย ๆ ตัว) ไม่เป็นไรใช้ D1 ก็ดีเหมือนกัน
pnpm wrangler d1 create banks-transactions
ตรงนี้จะได้ database_name
binding
และ database_id
มา อย่าลืมเอาไปเซฟใน wrangler.toml
ด้วย
- เซ็ตอัปฐานข้อมูล สร้างไฟล์
schema.sql
ที่มีเนื้อหาเป็นไฟล์ SQL ระบุโครงสร้างตารางที่จะใช้ เคสผมผมเน้นเก็บเนื้อหาอีเมล เลยจะใช้แค่ตารางเดียว
/* ./schema.sql */
DROP TABLE IF EXISTS transactions;
CREATE TABLE IF NOT EXISTS transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
sender VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
metadata TEXT NOT NULL
);
แล้ว apply ทั้ง remote และ local ด้วยคำสั่ง
pnpx wrangler d1 execute banks-transactions --local --file=./schema.sql
pnpx wrangler d1 execute banks-transactions --remote --file=./schema.sql
[!note] ด้วยวิธีนี้จะสามารถรันคำสั่ง SQL ได้ทั้งใน local และ remote ด้วย
pnpx wrangler d1 execute banks-transactions --local --command="SELECT * FROM transactions"
- มาถึงพระเอกจริง ๆ ของงาน โค้ดสำหรับให้ Worker จัดการอีเมลของเรา อย่าลืมติดตั้ง
postal-mime
ด้วยpnpm i postal-mime
ก่อน
// ./src/index.ts
import PostalMime, { type Email } from 'postal-mime';
export default {
async email(message, env, ctx) {
const email = await PostalMime.parse(message.raw);
function randomId() {
return Math.random().toString(36).substring(7);
}
try {
const metadata = {
cc: email.cc,
bcc: email.bcc,
subject: email.subject,
date: email.date,
headers: email.headers,
from: email.from,
messageId: email.messageId,
attachments: [],
deliveredTo: email.deliveredTo,
to: email.to,
inReplyTo: email.inReplyTo,
references: email.references,
replyTo: email.replyTo,
returnPath: email.returnPath,
sender: email.sender,
} satisfies Email;
const stmt = env.DB.prepare("INSERT INTO transactions (sender, content, metadata) VALUES (?1, ?2, ?3)");
await stmt.bind(message.from, email.html ?? email.text, JSON.stringify(metadata)).run();
} catch (err) {
console.error(err);
}
await message.forward(env.FORWARD);
}
} satisfies ExportedHandler<Env>;
แต่ยังไม่เสร็จ คุณอย่าลืมไปตั้งค่า Destination address ให้เรียบร้อยก่อน ไม่งั้นอีเมลจะส่งไม่ได้ แล้วใส่ FORWARD=your_dest@mail.com
ใน .dev.vars
ด้วย
แล้วยัด var นี้ใน secret ด้วย wrangler secret put FORWARD
แล้วกรอกอีเมลที่ว่า
- สุดท้ายก็เป็นการ deploy และเช็คว่าทำงานได้หรือไม่
pnpm wrangler deploy
- จัดแจงแก้อีเมลที่ให้ธนาคารส่งอีเมลหา ซึ่งคุณควรจะได้อีเมลเป็นปกติ เพิ่มเติมคือมีฐานข้อมูลที่เก็บข้อมูลอีเมลที่ส่งมา แล้วสามารถเข้าถึงได้จาก D1 ที่เราสร้างไว้ อย่าเผลอสร้าง endpoint อะไรเปิดข้อมูลนี้ให้คนอื่นเข้าถึงได้แล้วกัน
ด้วยสมัยนี้ผมเชื่อเหลือเกินว่า AI สามารถช่วยคุณได้เลยเขียนคร่าว ๆ แห่ะ ๆ เมื่อไหร่ที่ต้องการจะเข้าถึงข้อมูลที่เก็บก็อ่านจาก D1 ที่ว่าเอา วันนี้พอแค่นี้ก่อนดีกว่า ผมเพิ่งทำเสร็จ ถ้าลองแยกข้อมูลสำหรับ import เข้า cashew แล้วเดี๋ยวมาแชร์ต่อตอนถัดไป…