misarmail 5.0.3 copy "misarmail: ^5.0.3" to clipboard
misarmail: ^5.0.3 copied to clipboard

Send transactional email and run marketing campaigns from Dart: MisarMail's API for sends, campaigns, contacts, templates, domains, validation, analytics, webhooks and SSE.

MisarMail Dart SDK #

Send transactional email and run marketing campaigns from Dart — servers, CLIs and background isolates.

pub dart license

33 resource groups · 90 methods · SSE streaming · webhook signature verification

MisarMail is one API for both halves of your email: the receipts and password resets your product sends, and the campaigns, segments and automations your marketing team runs on the same contact list and the same verified domains.

Dart >=3.0.0 <4.0.0, depending only on package:http and package:crypto. Every method returns Future<Map<String, dynamic>> — the decoded JSON envelope, not a generated model.


Install #

dart pub #

dart pub add misarmail

pubspec.yaml #

dependencies:
  misarmail: ^5.0.3

Authentication #

Create a developer key at https://mail.misar.io/developers. It starts with msk_ and is sent as Authorization: Bearer msk_….

Every call is metered against the subscription attached to that key. There is no client-side limit checking — the server decides, and the SDK surfaces its answer. A plan refusal answers 403 with code: "plan_limit_exceeded" and is never retried.

import 'package:misarmail/misarmail.dart';

final mail = MisarMailClient(apiKey: Platform.environment['MISARMAIL_API_KEY']!);

Resources #

Every group the client exposes, and every public method on it.

Send #

Resource Methods What it covers
mail.email send Transactional send — cc/bcc/reply-to, tags, metadata, idempotency_key.
mail.sandbox send, list, delete Test sends captured instead of delivered.

Campaigns and tests #

Resource Methods What it covers
mail.campaigns list, create, get, update, send, delete Marketing campaigns: draft, edit, queue for send.
mail.abTests list, create, get, setWinner Subject, content, send-time, from-name and preheader splits, and winner selection.

Audience #

Resource Methods What it covers
mail.contacts list, create, get, update, delete, importContacts Subscribers, plus bulk import.
mail.segments members Dynamic audience segments and their membership.
mail.landingPages create Hosted landing pages with an email capture form.

Content #

Resource Methods What it covers
mail.templates list, create, get, update, delete, render Reusable templates and server-side variable rendering.
mail.ai subjectLines AI-generated subject lines.

Automations #

Resource Methods What it covers
mail.automations list, create, get, update, delete, activate Trigger-based workflows — welcome series, drips, re-engagement.

Deliverability and sending infrastructure #

Resource Methods What it covers
mail.domains list, create, get, verify, delete Sending domains and their DNS verification.
mail.dmarc check, listDomains, addDomain, removeDomain Live SPF/DKIM/DMARC record checks and monitored domains.
mail.deliverability audit, score Deliverability score, audit and remediation guidance.
mail.dedicatedIps list, create, update, delete Dedicated sending IPs.
mail.warmup get IP/domain warm-up progress and today's remaining capacity.
mail.inbound list, create, get, delete Inbound routing domains, so replies land in the unified inbox.

Mailbox and inbox #

Resource Methods What it covers
mail.emails list, get, update Stored messages in the mailbox.
mail.emailAccounts list Connected mailbox accounts.

Analytics and attribution #

Resource Methods What it covers
mail.analytics overview Delivery and engagement stats — aggregate, or one campaign.
mail.track event, purchase Custom events and ecommerce purchases.
mail.revenue attribution Revenue attributed back to email.
mail.usage get Metered usage for a period.

Validation #

Resource Methods What it covers
mail.validate email Address validation, and the credit balance behind it.

Plan, billing and credits #

Resource Methods What it covers
mail.plan get, monetization Current plan, quotas and monetization stats.
mail.billing subscription, checkout Subscription state and checkout.
mail.subscription get, upsert, cancel Subscription read/write and per-product plan limits.
mail.wallet get, credit, debit Credit balance, credit and debit.
mail.creditRates list What each metered action costs in credits.
mail.teamMembers get Team members on the account.
mail.monetization tip Newsletter tips.

Developer #

Resource Methods What it covers
mail.keys list, create, get, revoke API keys — create, list, revoke.
mail.webhooks list, create, get, update, delete, test Webhook endpoints, plus a test delivery.
mail.streaming generateEmail, campaignSend The two Server-Sent Events endpoints.

Client #

Thing Detail
Entry point MisarMailClient(apiKey:, baseUrl:, maxRetries:, httpClient:) — every resource is a field.
baseUrl https://api.misar.io/mail/v1
Results Future<Map<String, dynamic>> — read response['data'], response['pagination'], …
Transport One http.Client, reused. Call mail.close() when done. No timeout of its own — supply an http.Client that imposes one.
Retried 429, 500, 502, 503, 504 and transport failures — maxRetries attempts in total (3 by default), 500 ms then 1 s.
Never retried Plan refusals, and streams.
Errors MisarMailError, with MisarMailPlanLimitError and MisarMailNetworkError extending it.
Webhook verifier verifyWebhookSignature / signWebhook, defaultToleranceSeconds 300.
Flutter Use this package. misarmail_flutter is discontinued on pub.dev and marked replacedBy: misarmail.

Quick start #

import 'package:misarmail/misarmail.dart';

Future<void> main() async {
  final mail = MisarMailClient(apiKey: 'msk_your_key');

  final sent = await mail.email.send({
    'from': {'email': 'you@yourdomain.com', 'name': 'Your App'},
    'to': [
      {'email': 'someone@example.com'}
    ],
    'subject': 'Hello',
    'html': '<p>Hi there</p>',
  });

  print(sent['message_id']);
  mail.close();
}

Primary calls #

Send a transactional email, safely repeatable:

await mail.email.send({
  'from': {'email': 'you@yourdomain.com'},
  'to': [
    {'email': 'someone@example.com'}
  ],
  'subject': 'Your receipt',
  'html': '<p>Thanks for your order.</p>',
  'text': 'Thanks for your order.',
  'idempotency_key': 'order-1001-receipt',
  'tags': ['receipt'],
  'metadata': {'order_id': '1001'},
});

List contacts (the response carries data and pagination):

final page = await mail.contacts.list(
  params: {'page': 1, 'limit': 50, 'status': 'subscribed'},
);

for (final contact in page['data'] as List) {
  print(contact['email']);
}
print(page['pagination']['total']);

Create a contact:

final created = await mail.contacts.create({
  'email': 'new@example.com',
  'firstName': 'Ada',
  'lastName': 'Lovelace',
  'tags': ['beta'],
});

final contactId = created['data']['id'] as String;

Bulk import (max 5000 rows per call):

final result = await mail.contacts.importContacts({
  'contacts': [
    {'email': 'a@example.com', 'firstName': 'A'},
    {'email': 'b@example.com', 'firstName': 'B'},
  ],
  'updateExisting': true,
});

print(result['summary']); // {imported, updated, skipped, errors}

Create a campaign and send it. Campaigns take fromName and fromEmail — there is no from object here, unlike a transactional send:

final campaign = await mail.campaigns.create({
  'name': 'August launch',
  'subject': 'We just shipped',
  'fromName': 'Your Name',
  'fromEmail': 'you@yourdomain.com',
  'bodyHtml': '<h1>New release</h1>',
});

final queued = await mail.campaigns.send(campaign['data']['id'] as String);
print(queued['status']); // scheduled

Validate an address:

final check = await mail.validate.email('someone@example.com');
print(check['data']['is_valid']);
print(check['credits']['balance_after']);

Render a template with variables:

final rendered = await mail.templates.render({
  'template_id': 'tmpl_123',
  'variables': {'first_name': 'Ada'},
});

print(rendered['data']['subject']);
print(rendered['data']['html']);

Track an event and a purchase. Note event_name (not event) and total_cents (not amount):

await mail.track.event({
  'email': 'someone@example.com',
  'event_name': 'trial_started',
  'event_data': {'plan': 'pro'},
});

await mail.track.purchase({
  'email': 'someone@example.com',
  'order_id': 'ord_1001',
  'total_cents': 4900,
  'currency': 'USD',
  'items': [
    {'name': 'Pro plan', 'quantity': 1, 'price_cents': 4900},
  ],
});

Check plan standing before an expensive call, rather than discovering the ceiling through a refusal:

final plan = await mail.plan.get();
print(plan['plan']);    // current plan
print(plan['sending']); // per-day and per-month email caps
print(plan['usage']);   // one entry per metered feature: used / limit / remaining

A null limit means unlimited, and remaining is null alongside it rather than 0.

Errors #

Every non-2xx response raises MisarMailError, which carries status, message, errorType and details, plus the convenience getters isUnauthorized, isPlanDenied and isRetryable. Two subclasses narrow it:

  • MisarMailPlanLimitError — the subscription behind the key does not cover the call. Adds plan, upgradeUrl, retryAfter and feature.
  • MisarMailNetworkError — a transport failure, reported with status 0.

A plan refusal answers 403 carrying code: "plan_limit_exceeded" in the body. The client keys on that body marker rather than on the status, so the same refusal arriving as 402 or 429 is still typed as a plan limit — and it is never retried, because retrying cannot help until the allowance resets or the plan changes. A plain rate-limit 429, which carries no such marker, still is retried.

Catch from most specific to least:

try {
  await mail.campaigns.send(campaignId);
} on MisarMailPlanLimitError catch (e) {
  print('${e.feature} exhausted on ${e.plan} — upgrade at ${e.upgradeUrl}');
  if (e.retryAfter != null) print('resets in ${e.retryAfter}s');
} on MisarMailNetworkError catch (e) {
  print('transport failure: ${e.message}');
} on MisarMailError catch (e) {
  print('${e.status} ${e.errorType}: ${e.message}');
}

Streaming #

Two endpoints stream Server-Sent Events. Both sit outside /v1, which the SDK handles for you.

Method Route
streaming.generateEmail(options) POST /api/ai/generate-email/stream
streaming.campaignSend(campaignId) GET /api/campaigns/{id}/send-stream

Frames are unnamed — data: {…} with no event: line — so MisarMailStreamEvent.event is normally null and MisarMailStreamEvent.data holds the decoded JSON. The stream ends at the data: [DONE] sentinel, which the SDK consumes rather than handing on, and keepalive comments are skipped. A stream is never retried: replaying one that failed mid-flight would duplicate whatever you had already read.

import 'dart:io';
import 'package:misarmail/misarmail.dart';

await for (final event in mail.streaming.generateEmail({'prompt': 'a launch email'})) {
  stdout.write(event.data['delta'] ?? '');
}

await for (final event in mail.streaming.campaignSend(campaignId)) {
  print(event.data);
}

A plan refusal on opening the stream raises MisarMailPlanLimitError before any frame arrives.


Webhook signature verification #

This SDK does ship a verifier. MisarMail signs each webhook as HMAC-SHA256(timestamp + "." + rawBody) and sends the digest in X-Misar-Signature with the unix timestamp in X-Misar-Timestamp. Verify against the raw body — re-encoding a decoded map changes key order and whitespace, and therefore the digest. The comparison is constant-time and stale timestamps are rejected (defaultToleranceSeconds is 300).

import 'package:misarmail/misarmail.dart';

final ok = verifyWebhookSignature(
  payload: rawBody,
  signature: headers['x-misar-signature']!,
  timestamp: headers['x-misar-timestamp']!,
  secret: endpointSigningSecret,
  // toleranceSeconds: 300,
);

signWebhook(payload, timestamp, secret) produces the same digest, which is what you want when writing tests for your own webhook consumer.


MIT © Misar AI

1
likes
140
points
205
downloads

Documentation

Documentation
API reference

Publisher

verified publishermisar.io

Weekly Downloads

Send transactional email and run marketing campaigns from Dart: MisarMail's API for sends, campaigns, contacts, templates, domains, validation, analytics, webhooks and SSE.

Homepage
Repository (GitHub)
View/report issues

Topics

#email #transactional-email #marketing #api-client #webhooks

License

MIT (license)

Dependencies

crypto, http

More

Packages that depend on misarmail