This tutorial shows how to build custom Mau applications using the Go package. You’ll create a simple social app from scratch.
Time: ~20 minutes
Prerequisites:
The Mau Go package lets you:
1mkdir mau-app
2cd mau-app
3go mod init example.com/mau-app
4go get github.com/mau-network/mau@latest
Create main.go:
1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "path/filepath"
8
9 "github.com/mau-network/mau"
10)
11
12func main() {
13 // Mau directory path
14 mauDir := filepath.Join(os.Getenv("HOME"), ".mau-app")
15
16 // Check if account exists
17 accountFile := filepath.Join(mauDir, ".mau", "account.pgp")
18 var account *mau.Account
19 var err error
20
21 if _, err := os.Stat(accountFile); os.IsNotExist(err) {
22 // Create new account
23 fmt.Println("Creating new account...")
24 fmt.Print("Name: ")
25 var name string
26 fmt.Scanln(&name)
27
28 fmt.Print("Email: ")
29 var email string
30 fmt.Scanln(&email)
31
32 fmt.Print("Passphrase: ")
33 var passphrase string
34 fmt.Scanln(&passphrase)
35
36 account, err = mau.NewAccount(mauDir, name, email, passphrase)
37 if err != nil {
38 log.Fatalf("Failed to create account: %v", err)
39 }
40 fmt.Println("✓ Account created!")
41 } else {
42 // Open existing account
43 fmt.Print("Enter passphrase: ")
44 var passphrase string
45 fmt.Scanln(&passphrase)
46
47 account, err = mau.OpenAccount(mauDir, passphrase)
48 if err != nil {
49 log.Fatalf("Failed to open account: %v", err)
50 }
51 fmt.Println("✓ Account opened!")
52 }
53
54 // Display account info
55 fmt.Printf("\nName: %s\n", account.Name())
56 fmt.Printf("Email: %s\n", account.Email())
57 fmt.Printf("Fingerprint: %s\n", account.Fingerprint())
58}
Run it:
1go run main.go
Output:
Creating new account...
Name: Alice
Email: alice@example.com
Passphrase: [your-password]
✓ Account created!
Name: Alice
Email: alice@example.com
Fingerprint: 5D000B2F2C040A1675B49D7F0C7CB7DC36999D56
Add a function to create posts:
1package main
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "log"
8 "os"
9 "path/filepath"
10 "time"
11
12 "github.com/mau-network/mau"
13)
14
15// Post represents a social media posting
16type Post struct {
17 Context string `json:"@context"`
18 Type string `json:"@type"`
19 Headline string `json:"headline"`
20 ArticleBody string `json:"articleBody,omitempty"`
21 Author Author `json:"author"`
22 DatePublished time.Time `json:"datePublished"`
23}
24
25type Author struct {
26 Type string `json:"@type"`
27 Name string `json:"name"`
28 Identifier string `json:"identifier"`
29}
30
31func createPost(account *mau.Account, headline, body string) error {
32 post := Post{
33 Context: "https://schema.org",
34 Type: "SocialMediaPosting",
35 Headline: headline,
36 ArticleBody: body,
37 Author: Author{
38 Type: "Person",
39 Name: account.Name(),
40 Identifier: account.Fingerprint().String(),
41 },
42 DatePublished: time.Now(),
43 }
44
45 // Marshal to JSON
46 postJSON, err := json.MarshalIndent(post, "", " ")
47 if err != nil {
48 return fmt.Errorf("failed to marshal post: %w", err)
49 }
50
51 // Create file from JSON
52 reader := bytes.NewReader(postJSON)
53 filename := fmt.Sprintf("post-%d.json", time.Now().Unix())
54
55 // Add file (encrypted for yourself = public post)
56 file, err := account.AddFile(reader, filename, nil)
57 if err != nil {
58 return fmt.Errorf("failed to add file: %w", err)
59 }
60
61 fmt.Printf("✓ Post created: %s\n", file.Name())
62 return nil
63}
64
65func main() {
66 // ... (previous account setup code) ...
67
68 // Create a post
69 fmt.Println("\n--- Create a Post ---")
70 if err := createPost(account, "Hello from Go!", "This post was created programmatically."); err != nil {
71 log.Fatalf("Failed to create post: %v", err)
72 }
73}
Run it:
1go run main.go
Output:
✓ Account opened!
...
--- Create a Post ---
✓ Post created: post-1709107200.json
Add a function to list files:
1func listPosts(account *mau.Account) error {
2 // List all your files
3 files := account.ListFiles(account.Fingerprint(), time.Time{}, 100)
4
5 fmt.Printf("\n📝 Your posts (%d):\n\n", len(files))
6
7 for _, file := range files {
8 // Decrypt and parse
9 content, err := file.Read()
10 if err != nil {
11 log.Printf("Error reading %s: %v", file.Name(), err)
12 continue
13 }
14
15 var post Post
16 if err := json.Unmarshal(content, &post); err != nil {
17 log.Printf("Error parsing %s: %v", file.Name(), err)
18 continue
19 }
20
21 fmt.Printf("🔹 %s\n", post.Headline)
22 if post.ArticleBody != "" {
23 fmt.Printf(" %s\n", post.ArticleBody)
24 }
25 fmt.Printf(" 📅 %s\n\n", post.DatePublished.Format("Jan 2, 2006 15:04"))
26 }
27
28 return nil
29}
30
31func main() {
32 // ... (previous code) ...
33
34 // List posts
35 if err := listPosts(account); err != nil {
36 log.Fatalf("Failed to list posts: %v", err)
37 }
38}
Add friend management:
1func addFriend(account *mau.Account, publicKeyPath string) error {
2 // Open friend's public key file
3 file, err := os.Open(publicKeyPath)
4 if err != nil {
5 return fmt.Errorf("failed to open key file: %w", err)
6 }
7 defer file.Close()
8
9 // Add friend
10 friend, err := account.AddFriend(file)
11 if err != nil {
12 return fmt.Errorf("failed to add friend: %w", err)
13 }
14
15 fmt.Printf("✓ Friend added: %s <%s>\n", friend.Name(), friend.Email())
16 fmt.Printf(" Fingerprint: %s\n", friend.Fingerprint())
17
18 return nil
19}
20
21func listFriends(account *mau.Account) error {
22 keyring, err := account.ListFriends()
23 if err != nil {
24 return fmt.Errorf("failed to list friends: %w", err)
25 }
26
27 friends := keyring.All()
28 fmt.Printf("\n👥 Friends (%d):\n\n", len(friends))
29
30 for _, friend := range friends {
31 fmt.Printf("• %s <%s>\n", friend.Name(), friend.Email())
32 fmt.Printf(" Fingerprint: %s\n", friend.Fingerprint())
33
34 // Check if following
35 follows, _ := account.ListFollows()
36 following := false
37 for _, f := range follows {
38 if f.Fingerprint() == friend.Fingerprint() {
39 following = true
40 break
41 }
42 }
43
44 if following {
45 fmt.Printf(" Status: Following ✓\n")
46 } else {
47 fmt.Printf(" Status: Not following\n")
48 }
49 fmt.Println()
50 }
51
52 return nil
53}
54
55func main() {
56 // ... (previous code) ...
57
58 // Example: Add a friend from file
59 // friendKeyPath := "/path/to/friend-pubkey.pgp"
60 // if err := addFriend(account, friendKeyPath); err != nil {
61 // log.Printf("Failed to add friend: %v", err)
62 // }
63
64 // List friends
65 if err := listFriends(account); err != nil {
66 log.Fatalf("Failed to list friends: %v", err)
67 }
68}
1func followFriend(account *mau.Account, fingerprint mau.Fingerprint) error {
2 // Get friend from keyring
3 keyring, err := account.ListFriends()
4 if err != nil {
5 return fmt.Errorf("failed to list friends: %w", err)
6 }
7
8 friend, err := keyring.Get(fingerprint)
9 if err != nil {
10 return fmt.Errorf("friend not found: %w", err)
11 }
12
13 // Follow
14 if err := account.Follow(friend); err != nil {
15 return fmt.Errorf("failed to follow: %w", err)
16 }
17
18 fmt.Printf("✓ Now following: %s\n", friend.Name())
19 return nil
20}
21
22func syncFromFriend(account *mau.Account, friendURL string, friendFPR mau.Fingerprint) error {
23 // Create HTTP client for the friend
24 // Note: This is simplified; real implementation needs DNS names from discovery
25 client, err := account.Client(friendFPR, []string{friendURL})
26 if err != nil {
27 return fmt.Errorf("failed to create client: %w", err)
28 }
29
30 // Get last sync time
31 lastSync := account.GetLastSyncTime(friendFPR)
32 fmt.Printf("📥 Syncing from %s (since %v)...\n", friendFPR.String()[:8], lastSync)
33
34 // Sync files
35 if err := client.Sync(lastSync); err != nil {
36 return fmt.Errorf("sync failed: %w", err)
37 }
38
39 fmt.Println("✓ Sync complete!")
40 return nil
41}
1func startServer(account *mau.Account, port string) error {
2 // Create server instance
3 // knownNodes can be empty for local-only operation
4 server, err := account.Server(nil)
5 if err != nil {
6 return fmt.Errorf("failed to create server: %w", err)
7 }
8
9 fmt.Printf("🚀 Starting server on :%s\n", port)
10 fmt.Printf(" Fingerprint: %s\n", account.Fingerprint())
11
12 // Start server (blocking)
13 if err := server.Listen(":" + port); err != nil {
14 return fmt.Errorf("server error: %w", err)
15 }
16
17 return nil
18}
19
20func main() {
21 // ... (previous code) ...
22
23 // Start server (comment out for non-server mode)
24 // if err := startServer(account, "8080"); err != nil {
25 // log.Fatalf("Server error: %v", err)
26 // }
27}
Complete example application:
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "os"
8 "path/filepath"
9 "time"
10
11 "github.com/mau-network/mau"
12)
13
14type FeedItem struct {
15 Headline string `json:"headline"`
16 Body string `json:"articleBody"`
17 Author string `json:"-"`
18 AuthorFPR string `json:"-"`
19 DatePublished time.Time `json:"datePublished"`
20 Filename string `json:"-"`
21}
22
23func getFeed(account *mau.Account) ([]FeedItem, error) {
24 var feed []FeedItem
25
26 // Get your own posts
27 myFiles := account.ListFiles(account.Fingerprint(), time.Time{}, 50)
28 for _, file := range myFiles {
29 item, err := parseFeedItem(file, account.Name(), account.Fingerprint().String())
30 if err == nil {
31 feed = append(feed, item)
32 }
33 }
34
35 // Get friends' posts
36 follows, err := account.ListFollows()
37 if err != nil {
38 return nil, err
39 }
40
41 for _, friend := range follows {
42 friendFiles := account.ListFiles(friend.Fingerprint(), time.Time{}, 50)
43 for _, file := range friendFiles {
44 item, err := parseFeedItem(file, friend.Name(), friend.Fingerprint().String())
45 if err == nil {
46 feed = append(feed, item)
47 }
48 }
49 }
50
51 // Sort by date (newest first)
52 // (Implement sorting logic here)
53
54 return feed, nil
55}
56
57func parseFeedItem(file *mau.File, authorName, authorFPR string) (FeedItem, error) {
58 content, err := file.Read()
59 if err != nil {
60 return FeedItem{}, err
61 }
62
63 var post struct {
64 Type string `json:"@type"`
65 Headline string `json:"headline"`
66 ArticleBody string `json:"articleBody"`
67 DatePublished time.Time `json:"datePublished"`
68 }
69
70 if err := json.Unmarshal(content, &post); err != nil {
71 return FeedItem{}, err
72 }
73
74 // Only include social media postings
75 if post.Type != "SocialMediaPosting" {
76 return FeedItem{}, fmt.Errorf("not a social media posting")
77 }
78
79 return FeedItem{
80 Headline: post.Headline,
81 Body: post.ArticleBody,
82 Author: authorName,
83 AuthorFPR: authorFPR,
84 DatePublished: post.DatePublished,
85 Filename: file.Name(),
86 }, nil
87}
88
89func displayFeed(account *mau.Account) error {
90 feed, err := getFeed(account)
91 if err != nil {
92 return err
93 }
94
95 fmt.Printf("\n📰 Your Feed (%d posts)\n", len(feed))
96 fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
97
98 for _, item := range feed {
99 fmt.Printf("\n👤 %s (%s...)\n", item.Author, item.AuthorFPR[:8])
100 fmt.Printf("🔹 %s\n", item.Headline)
101 if item.Body != "" {
102 fmt.Printf(" %s\n", item.Body)
103 }
104 fmt.Printf("📅 %s\n", item.DatePublished.Format("Jan 2, 2006 15:04"))
105 }
106
107 fmt.Println("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
108 return nil
109}
110
111func main() {
112 mauDir := filepath.Join(os.Getenv("HOME"), ".mau-app")
113
114 fmt.Print("Enter passphrase: ")
115 var passphrase string
116 fmt.Scanln(&passphrase)
117
118 account, err := mau.OpenAccount(mauDir, passphrase)
119 if err != nil {
120 log.Fatalf("Failed to open account: %v", err)
121 }
122
123 // Display feed
124 if err := displayFeed(account); err != nil {
125 log.Fatalf("Failed to display feed: %v", err)
126 }
127}
Build and run:
1go build -o mau-reader .
2./mau-reader
1type Message struct {
2 Context string `json:"@context"`
3 Type string `json:"@type"`
4 Text string `json:"text"`
5 Sender Author `json:"sender"`
6 Recipient Author `json:"recipient"`
7 DateSent time.Time `json:"dateSent"`
8}
9
10func sendMessage(account *mau.Account, recipientFPR mau.Fingerprint, text string) error {
11 // Get recipient friend
12 keyring, err := account.ListFriends()
13 if err != nil {
14 return err
15 }
16
17 recipient, err := keyring.Get(recipientFPR)
18 if err != nil {
19 return fmt.Errorf("recipient not found: %w", err)
20 }
21
22 msg := Message{
23 Context: "https://schema.org",
24 Type: "Message",
25 Text: text,
26 Sender: Author{
27 Type: "Person",
28 Identifier: account.Fingerprint().String(),
29 },
30 Recipient: Author{
31 Type: "Person",
32 Identifier: recipientFPR.String(),
33 },
34 DateSent: time.Now(),
35 }
36
37 msgJSON, err := json.Marshal(msg)
38 if err != nil {
39 return err
40 }
41
42 // Create file encrypted only for recipient
43 reader := bytes.NewReader(msgJSON)
44 filename := fmt.Sprintf("msg-to-%s-%d.json", recipientFPR.String()[:8], time.Now().Unix())
45
46 _, err = account.AddFile(reader, filename, []*mau.Friend{recipient})
47 if err != nil {
48 return err
49 }
50
51 fmt.Printf("✓ Message sent to %s\n", recipient.Name())
52 return nil
53}
1package main
2
3import (
4 "errors"
5 "fmt"
6 "github.com/mau-network/mau"
7)
8
9func robustAccountOpen(mauDir, passphrase string) (*mau.Account, error) {
10 account, err := mau.OpenAccount(mauDir, passphrase)
11 if err != nil {
12 // Check for specific errors
13 if errors.Is(err, mau.ErrIncorrectPassphrase) {
14 return nil, fmt.Errorf("incorrect passphrase, please try again")
15 }
16 if errors.Is(err, mau.ErrNoIdentity) {
17 return nil, fmt.Errorf("no account found at %s, run 'init' first", mauDir)
18 }
19 return nil, fmt.Errorf("failed to open account: %w", err)
20 }
21 return account, nil
22}
23
24func robustAddFriend(account *mau.Account, keyPath string) error {
25 file, err := os.Open(keyPath)
26 if err != nil {
27 return fmt.Errorf("cannot open key file: %w", err)
28 }
29 defer file.Close()
30
31 friend, err := account.AddFriend(file)
32 if err != nil {
33 return fmt.Errorf("failed to import friend key: %w", err)
34 }
35
36 fmt.Printf("✓ Added: %s\n", friend.Name())
37 return nil
38}
Recommended project layout:
mau-app/
├── main.go # Entry point
├── account.go # Account management
├── posts.go # Post creation/reading
├── friends.go # Friend management
├── sync.go # Syncing logic
├── server.go # HTTP server
├── types.go # JSON-LD type definitions
└── go.mod
✅ Create and open Mau accounts programmatically
✅ Create and share posts using Go structs
✅ List and read encrypted files
✅ Manage friends and follows
✅ Build custom sync logic
✅ Start HTTP servers
✅ Parse Schema.org JSON-LD content
✅ Send private messages
✅ Build complete social applications
| Function | Purpose |
|---|---|
mau.NewAccount(dir, name, email, pass) |
Create new account |
mau.OpenAccount(dir, pass) |
Open existing account |
account.AddFile(reader, name, recipients) |
Create encrypted file |
account.GetFile(fpr, name) |
Retrieve file |
account.ListFiles(fpr, after, limit) |
List files |
account.AddFriend(keyReader) |
Import friend’s key |
account.Follow(friend) |
Start following |
account.Client(fpr, addresses) |
Create sync client |
account.Server(peers) |
Create HTTP server |
file.Read() |
Decrypt and read content |
| Level | Best For | Complexity |
|---|---|---|
| Raw GPG | Understanding primitives | High |
| Mau CLI | End users, scripts | Low |
| Mau Package | Custom apps, integration | Medium |
Use the package when you need:
gui/ directory has a full implementationTip: Check out the test files (*_test.go) in the Mau repository for more usage examples!