Introducing the Plunk Email Library for Dart Developers

For Dart and Flutter developers looking to integrate robust email capabilities into their applications look no further than this OSS.

Introducing the Plunk Email Library for Dart Developers
Image created by author

Email remains one of the most effective channels for communicating with users, whether you’re sending transactional messages, marketing campaigns, or important notifications. For Dart and Flutter developers looking to integrate robust email capabilities into their applications, the Plunk library offers a comprehensive solution that’s easy to use and packed with features.

What is Plunk?

Plunk is an open source modern email platform backed by Amazon SES and designed specifically for SaaS applications. It provides powerful tools for sending transactional emails, managing contacts, and running email marketing campaigns. The plunk package is a Dart client that makes it easy to integrate with the Plunk API in your Dart or Flutter applications.

In this article, we’ll explore how to use the Plunk library to add sophisticated email capabilities to your applications.

Getting Started with Plunk

Installation

To get started, add the Plunk package to your pubspec.yaml file:

dependencies: 
  plunk: ^2.0.0

Run dart pub get or flutter pub get to install the package.

Basic Setup

Using Plunk starts with initializing the client with your API key:

import 'package:plunk/plunk.dart'; 
 
void main() { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  // Now you can use plunk to send emails, manage contacts, etc. 
}
Note: You’ll need to sign up for a Plunk account at useplunk.com to get an API key.

Sending Your First Email

Let’s start with the most common task: sending a transactional email:

Future<void> sendWelcomeEmail(String userEmail, String userName) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  try { 
    final response = await plunk.sendEmail( 
      to: [userEmail], 
      subject: 'Welcome to Our App!', 
      body: 'Hi $userName, thanks for signing up!', 
      subscribed: true, // Add the user to your contact list 
    ); 
     
    print('Email sent successfully: ${response.success}'); 
  } catch (e) { 
    print('Failed to send email: $e'); 
  } 
}

This simple example demonstrates how easy it is to send an email using Plunk. The sendEmail method returns a SendResponse object that contains information about the sent email, including whether it was successful and timestamps.

Core Features of the Plunk Library

The Plunk library offers a rich set of features for managing your email communications. Let’s dive into the key capabilities:

Contact Management

Plunk allows you to manage your contacts programmatically:

Creating Contacts

You can create contacts without sending them an email:

Future<void> addUserToContacts(String email, Map<String, dynamic> userData) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  final contact = await plunk.createContact( 
    email: email, 
    subscribed: true, 
    data: userData, // Custom data about your user 
  ); 
   
  print('Contact created with ID: ${contact.id}'); 
}

Retrieving Contact Information

You can fetch details about a specific contact:

Future<void> getContactDetails(String contactId) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  final contact = await plunk.getContact(id: contactId); 
   
  print('Contact details: ${contact.email}, Subscribed: ${contact.subscribed}'); 
  print('Custom data: ${contact.data}'); 
}

Managing Subscriptions

Plunk makes it easy to manage subscription status:

// Subscribe a contact 
await plunk.subscribeContact(email: 'user@example.com'); 
 
// Unsubscribe a contact 
await plunk.unsubscribeContact(email: 'user@example.com');

Updating Contact Data

You can update a contact’s information:

await plunk.updateContact( 
  id: 'contact_id', 
  data: { 
    'plan': 'premium', 
    'lastLogin': DateTime.now().toIso8601String(), 
  }, 
);

Event Tracking

Plunk allows you to track user events, which can be used to trigger automated emails:

Future<void> trackPurchase(String userEmail, String productId, double amount) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  await plunk.trackEvent( 
    email: userEmail, 
    event: 'purchase', 
    data: { 
      'productId': productId, 
      'amount': amount, 
      'date': DateTime.now().toIso8601String(), 
    }, 
  ); 
}

Campaign Management

For marketing emails, Plunk provides campaign management capabilities:

Creating a Campaign

Future<void> createNewsletterCampaign(String subject, String content, List<String> recipients) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  final campaign = await plunk.createCampaign( 
    subject: subject, 
    body: content, 
    recipients: recipients, 
    style: CampaignStyle.html, // Use HTML formatting 
  ); 
   
  print('Campaign created with ID: ${campaign.id}'); 
}

Sending a Campaign

Future<void> sendCampaign(String campaignId) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  final result = await plunk.sendCampaign( 
    id: campaignId, 
    live: true, // Set to false for testing 
  ); 
   
  if (result) { 
    print('Campaign sent successfully'); 
  } 
}

Advanced Usage

Error Handling

Plunk provides specific exception types to help you handle errors appropriately:

try { 
  await plunk.sendEmail( 
    to: ['invalid-email'], 
    subject: 'Test', 
    body: 'Test body', 
  ); 
} catch (e) { 
  if (e is PlunkInvalidRequestException) { 
    print('Invalid request: ${e.message}'); 
  } else if (e is PlunkAuthorizationException) { 
    print('Authorization error: ${e.message}'); 
  } else if (e is PlunkQuotaException) { 
    print('Quota exceeded: ${e.message}'); 
  } else if (e is PlunkUnknownException) { 
    print('Unknown error: ${e.message}'); 
  } 
}

Customizing Email Headers

You can customize the email headers for more control:

await plunk.sendEmail( 
  to: ['user@example.com'], 
  subject: 'Important notification', 
  body: 'Your account has been updated', 
  from: 'support@yourdomain.com', 
  name: 'Your App Support', 
  reply: 'no-reply@yourdomain.com', 
  headers: { 
    'X-Custom-Header': 'custom-value', 
    'X-Priority': '1', 
  }, 
);

Real-World Examples

Let’s explore a few real-world examples to demonstrate how Plunk can be integrated into your application workflows:

Example 1: User Registration Flow

Future<void> registerUser(String email, String name, String password) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  // 1. Create user in your database 
  final user = await createUserInDatabase(email, name, password); 
   
  // 2. Add user to Plunk contacts 
  await plunk.createContact( 
    email: email, 
    subscribed: true, 
    data: { 
      'name': name, 
      'registrationDate': DateTime.now().toIso8601String(), 
    }, 
  ); 
   
  // 3. Send welcome email 
  await plunk.sendEmail( 
    to: [email], 
    subject: 'Welcome to ${appName}!', 
    body: ''' 
      <h1>Welcome, ${name}!</h1> 
      <p>Thank you for joining our platform. We're excited to have you!</p> 
      <p>Get started by <a href="https://yourapp.com/getting-started">exploring our features</a>.</p> 
    ''', 
    name: appName, 
  ); 
   
  // 4. Track signup event 
  await plunk.trackEvent( 
    email: email, 
    event: 'user_registered', 
    data: { 
      'userId': user.id, 
    }, 
  ); 
}

Example 2: Password Reset Functionality

Future<void> initiatePasswordReset(String email) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  // 1. Generate reset token and store in your database 
  final resetToken = generateResetToken(); 
  await storeResetTokenInDatabase(email, resetToken); 
   
  // 2. Create reset URL 
  final resetUrl = 'https://yourapp.com/reset-password?token=$resetToken'; 
   
  // 3. Send password reset email 
  await plunk.sendEmail( 
    to: [email], 
    subject: 'Password Reset Request', 
    body: ''' 
      <h2>Password Reset Request</h2> 
      <p>We received a request to reset your password. If you didn't make this request, you can ignore this email.</p> 
      <p>To reset your password, click the link below:</p> 
      <p><a href="$resetUrl">Reset Password</a></p> 
      <p>This link will expire in 1 hour.</p> 
    ''', 
  ); 
   
  // 4. Track password reset request event 
  await plunk.trackEvent( 
    email: email, 
    event: 'password_reset_requested', 
  ); 
}

Example 3: Campaign for New Features

Future<void> announceNewFeatures(List<String> premiumUserEmails) async { 
  final plunk = Plunk(apiKey: 'YOUR_API_KEY'); 
   
  // 1. Create a campaign 
  final campaign = await plunk.createCampaign( 
    subject: 'Exciting New Features Just Released!', 
    body: ''' 
      <h1>We've Added New Features You'll Love</h1> 
      <p>As a premium user, you now have access to:</p> 
      <ul> 
        <li>Advanced reporting</li> 
        <li>Custom integrations</li> 
        <li>Priority support</li> 
      </ul> 
      <p><a href="https://yourapp.com/new-features">Learn More</a></p> 
    ''', 
    recipients: premiumUserEmails, 
    style: CampaignStyle.html, 
  ); 
   
  // 2. Send the campaign 
  await plunk.sendCampaign( 
    id: campaign.id, 
    live: true, 
    delay: 3600, // Delay send by 1 hour 
  ); 
}

Best Practices

Managing API Keys Securely

Never hardcode your Plunk API key in your application. Instead:

For server-side applications: Use environment variables
 — For Flutter apps: Use secure storage or a backend proxy

// Example using environment variables 
final apiKey = Platform.environment['PLUNK_API_KEY'] ?? ''; 
final plunk = Plunk(apiKey: apiKey);

Handling Rate Limits and Quotas

Plunk has rate limits and quotas based on your plan. Implement proper error handling:

try { 
  await plunk.sendEmail(/* ... */); 
} catch (e) { 
  if (e is PlunkQuotaException) { 
    // Log the error and implement backoff strategy 
    await Future.delayed(Duration(minutes: 5)); 
    // Try again or notify admin 
  } 
}

Testing Email Functionality

For testing, you might want to create a mock implementation:

class MockPlunk implements Plunk { 
  @override 
  Future<SendResponse> sendEmail({ 
    required List<String> to, 
    required String subject, 
    required String body, 
    bool subscribed = false, 
    String? name, 
    String? from, 
    String? reply, 
    Map<String, dynamic> headers = const {}, 
  }) async { 
    // Return a mock response 
    return SendResponse( 
      success: true, 
      emails: [], 
      timestamp: DateTime.now(), 
    ); 
  } 
   
  // Implement other methods... 
}

Conclusion

The Plunk library offers Dart and Flutter developers a powerful way to integrate email functionality into their applications. With features for sending transactional emails, managing contacts, tracking events, and running campaigns, it provides everything you need for sophisticated email communications.

The library’s clean API makes it easy to get started, while its comprehensive feature set ensures it can grow with your application’s needs. Whether you’re building a small side project or a large-scale SaaS application, Plunk provides the tools you need to communicate effectively with your users.

Resources

Plunk Official Website
 — Plunk API Documentation
 — Plunk Dart Package on pub.dev
 — GitHub Repository

Start adding powerful email capabilities to your Dart and Flutter applications today with the Plunk library!