Extract iMessage statistics for your year in review
Project description
iMessage Wrapped
A Python tool for extracting comprehensive messaging statistics from the macOS iMessage database. Generate a "year in review" analysis of your conversations with detailed metrics on message patterns, reactions, media sharing, and more.
Table of Contents
- Overview
- Requirements
- Installation
- Database Access
- Usage
- Output Format
- SQL Reference
- Database Schema
- Troubleshooting
- Privacy and Security
- License
Overview
iMessage Wrapped analyzes your iMessage conversation history and generates a JSON file containing statistics including:
- Total message counts (sent vs. received)
- Monthly and daily messaging patterns
- Peak texting hours and busiest days
- "I love you" counts and love emoji usage
- Pet name frequency (baby, babe, my love)
- Reaction statistics (hearts, likes, laughs)
- Media and attachment counts
- Voice memo and link sharing stats
- Response time analysis
- Double/triple text frequency
- Late night messaging patterns
- Message milestones (10k, 50k, 100k, etc.)
Requirements
- macOS 10.15 or later
- Python 3.8 or later
- Access to iMessage database (
chat.db) - Full Disk Access permission for Terminal (if reading directly from Messages)
Python Dependencies
This script uses only Python standard library modules:
sqlite3jsonreargparsecollectionsdatetimepathlib
No external packages are required.
Installation
Option 1: Install from PyPI (recommended)
pip install imessage-year-wrapped
Option 2: Install from GitHub
pip install git+https://github.com/dinesh-git17/imessage-wrapped.git
Option 3: Install from source
git clone https://github.com/dinesh-git17/imessage-wrapped.git
cd imessage-wrapped
pip install .
Development installation
git clone https://github.com/dinesh-git17/imessage-wrapped.git
cd imessage-wrapped
pip install -e ".[dev]" # Includes pytest, pylint, ruff
Database Access
The iMessage database is located at ~/Library/Messages/chat.db. There are two methods to access it:
Method 1: Copy the Database (Recommended)
The Messages app locks the database while running. Copy it to a working location:
# Quit Messages.app first, then:
cp ~/Library/Messages/chat.db ~/Desktop/chat.db
Method 2: Direct Access with Full Disk Access
- Open System Preferences > Security & Privacy > Privacy
- Select "Full Disk Access" from the sidebar
- Click the lock icon and authenticate
- Add Terminal.app (or your terminal emulator) to the list
- Restart Terminal
Accessing iPhone Messages via Backup
To analyze iPhone messages not synced to your Mac:
- Connect your iPhone and create a local backup using Finder
- Locate the backup at:
~/Library/Application Support/MobileSync/Backup/<DEVICE_ID>/ - The messages database is stored with a hashed filename:
3d/3d0d7e5fb2ce288813306e4d4636395e047a3d28 - Copy this file and rename it to
chat.db
Usage
Basic Usage
imessage-wrapped --phone <PHONE_NUMBER> --year <YEAR> --db <PATH_TO_DB>
Arguments
| Argument | Required | Default | Description |
|---|---|---|---|
--phone |
Yes | N/A | Contact's phone number (digits only, e.g., 5551234567) |
--year |
No | Current year | Year to analyze |
--db |
No | ~/Library/Messages/chat.db |
Path to chat.db file |
--output, -o |
No | imessage_wrapped.json |
Output JSON file path |
Examples
Analyze 2025 messages with a contact:
imessage-wrapped --phone 5551234567 --year 2025 --db ~/Desktop/chat.db
Specify a custom output file:
imessage-wrapped --phone 5551234567 --year 2025 --output stats_2025.json
Analyze the current year using the default database location:
imessage-wrapped --phone 5551234567
Finding a Contact's Phone Number
To find the exact format of a phone number in the database:
sqlite3 ~/Desktop/chat.db "SELECT ROWID, id FROM handle WHERE id LIKE '%555%';"
Phone numbers may be stored with or without country codes. Try variations if your initial query returns no results.
Output Format
The script generates a JSON file with the following structure:
{
"meta": {
"year": 2025,
"generatedAt": "2025-12-27T10:30:00.000000",
"daysTexting": 342
},
"classics": {
"totalMessages": 45000,
"youSent": 22000,
"themSent": 23000,
"avgPerDay": 131,
"byMonth": [...],
"busiestDays": [...],
"peakHours": [...],
"busiestSingleDays": [...],
"first": {...},
"last": {...}
},
"love": {
"iLoveYou": {"you": 150, "them": 175, "total": 325},
"heartReactions": {"you": 500, "them": 450},
"emojis": [...],
"petNames": [...]
},
"patterns": {
"avgResponseTime": {"you": 3.5, "them": 4.2},
"doubleTexts": {"you": 1200, "them": 800},
"longestSilence": {...}
},
"media": {
"attachments": {"you": 300, "them": 250, "total": 550},
"voiceMemos": {"you": 45, "them": 30},
"links": {"you": 120, "them": 80},
"reactions": [...]
},
"wordStats": {
"laughing": [...],
"questions": {"you": 2000, "them": 1800},
"allCaps": {"you": 150, "them": 100},
"topEmojis": [...]
},
"milestones": [...],
"lateNight": {
"total": 500,
"you": 280,
"them": 220,
"morningFirst": {"you": 180, "them": 162},
"goodnightLast": {"you": 175, "them": 167}
}
}
Field Descriptions
meta
year: The year analyzedgeneratedAt: ISO 8601 timestamp of when the analysis was rundaysTexting: Number of unique days with at least one message
classics
totalMessages: Combined message count for both partiesyouSent/themSent: Individual message countsavgPerDay: Average messages per day (total / daysTexting)byMonth: Array of monthly breakdowns withmonth,you,themfieldsbusiestDays: Days of week ranked by message countpeakHours: Top 5 busiest hours (12-hour format)busiestSingleDays: Top 5 individual dates by message countfirst/last: First and last messages of the year with date, sender, and text preview
love
iLoveYou: Count of messages containing "i love you" (case-insensitive)heartReactions: iMessage heart reactions given by each partyemojis: Counts for specific love-related emojispetNames: Counts for terms of endearment (baby, babe, my love)
patterns
avgResponseTime: Average response time in minutes (for responses under 60 minutes)doubleTexts: Count of consecutive messages from the same senderlongestSilence: Longest gap between messages with start time, end time, and duration in hours
media
attachments: Messages containing any attachmentvoiceMemos: Audio messages (.caf,.m4afiles)links: Messages containing HTTP/HTTPS URLsreactions: Breakdown by reaction type (love, like, dislike, laugh, emphasis, question)
wordStats
laughing: Counts for "haha", "lol", and the laughing emojiquestions: Messages containing question marksallCaps: Messages in all capital letters (minimum 6 characters)topEmojis: 15 most frequently used emojis
milestones
Array of message count milestones (10k, 25k, 50k, etc.) with the date each was reached
lateNight
total/you/them: Messages sent between midnight and 4 AMmorningFirst: Who sends the first message of the day (5 AM to noon) more oftengoodnightLast: Who sends the last message of the night (9 PM to 5 AM) more often
SQL Reference
The repository includes imessage-wrapped-sql-reference.md, a comprehensive guide containing:
- Raw SQL queries for each statistic
- Date conversion formulas for Apple timestamps
- Table schema documentation
- Query examples for custom analysis
Use this reference to run individual queries or extend the analysis.
Database Schema
Key Tables
| Table | Description |
|---|---|
message |
All messages with text, timestamps, and sender info |
handle |
Contact identifiers (phone numbers, emails) |
chat |
Chat/conversation metadata |
chat_message_join |
Links messages to chats |
attachment |
Attachment metadata (photos, videos, files) |
message_attachment_join |
Links messages to attachments |
Important Columns
| Column | Table | Description |
|---|---|---|
text |
message | Message content |
date |
message | Apple timestamp (nanoseconds since 2001-01-01) |
is_from_me |
message | 1 if sent by you, 0 if received |
cache_has_attachments |
message | 1 if message has attachments |
associated_message_type |
message | Reaction type (2000-2005) |
id |
handle | Phone number or email address |
Apple Timestamp Conversion
Apple stores dates as nanoseconds since January 1, 2001. To convert:
-- To readable datetime
datetime(message.date/1000000000 + 978307200, 'unixepoch', 'localtime')
-- To filter by year
WHERE message.date >= strftime('%s', '2025-01-01') * 1000000000 - 978307200000000000
AND message.date < strftime('%s', '2026-01-01') * 1000000000 - 978307200000000000
Reaction Types
| Value | Reaction |
|---|---|
| 2000 | Loved (heart) |
| 2001 | Liked (thumbs up) |
| 2002 | Disliked (thumbs down) |
| 2003 | Laughed |
| 2004 | Emphasized (exclamation) |
| 2005 | Questioned |
| 3000-3005 | Removed reactions (corresponding to above) |
Troubleshooting
"Database not found" Error
Ensure the path to chat.db is correct. If using the default location, verify Messages.app has synced your messages.
ls -la ~/Library/Messages/chat.db
"Database is locked" Error
Quit the Messages application before running the script, or copy the database to a different location.
Permission Denied
Grant Full Disk Access to Terminal:
- System Preferences > Security & Privacy > Privacy
- Select "Full Disk Access"
- Add Terminal.app
No Messages Found for Contact
Verify the phone number format matches what is stored in the database:
sqlite3 ~/Desktop/chat.db "SELECT DISTINCT id FROM handle;"
Phone numbers may include country codes (+1) or be formatted differently than expected.
Incorrect Message Counts
The script filters by year. Verify the date range is correct for your data:
sqlite3 ~/Desktop/chat.db "SELECT MIN(date), MAX(date) FROM message;"
Convert the returned timestamps using the formula in the Database Schema section.
Privacy and Security
This tool operates entirely locally. No data is transmitted externally.
Recommendations
- Do not commit
chat.dbor output JSON files to version control - Delete copied database files after analysis
- Store output files securely if they contain sensitive conversation data
- Add
*.jsonandchat.dbto your.gitignore
Data Handling
The script reads from the database in read-only mode. No modifications are made to the original iMessage database.
License
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file imessage_year_wrapped-1.0.0.tar.gz.
File metadata
- Download URL: imessage_year_wrapped-1.0.0.tar.gz
- Upload date:
- Size: 22.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d3e0dbee9ac54e257f8b0b0c3f5bb25f28521f55f3be24121eb8ad971cc183f
|
|
| MD5 |
33116878e2528f9263d50f127e980bcd
|
|
| BLAKE2b-256 |
408c2f662955b9ab1e25454a92734a8e1d7e452af5afd0bf0aaf313b171f8a10
|
File details
Details for the file imessage_year_wrapped-1.0.0-py3-none-any.whl.
File metadata
- Download URL: imessage_year_wrapped-1.0.0-py3-none-any.whl
- Upload date:
- Size: 14.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7dd8fbdf952d660a3ae8df8988268222e35622e94078a846c092d75fc73411fe
|
|
| MD5 |
8f9c8e93b69295b92063aa9cd8a4fcbe
|
|
| BLAKE2b-256 |
7a14bec15e71cfef24968704a748bf148a8edf5366a599d90812fb1c5284cb03
|