Magic-Mail — API Reference
Core services
strapi.plugin('email').service('email').send(options)
Standard Strapi email service. Magic-Mail intercepts this automatically — no code changes required when migrating from @strapi/provider-email-nodemailer etc.
await strapi.plugin('email').service('email').send({
to: string | string[],
from?: string,
cc?: string | string[],
bcc?: string | string[],
replyTo?: string,
subject: string,
text?: string,
html?: string,
attachments?: Attachment[],
// Magic-Mail extensions (all optional):
type?: 'transactional' | 'marketing' | 'notification' | 'system',
priority?: 'high' | 'normal' | 'low',
accountName?: string, // Force a specific account
customField?: string, // Used by routing rules
unsubscribeUrl?: string, // Adds List-Unsubscribe headers
templateId?: number, // Use a Magic-Mail template
templateData?: Record<string, any>, // Template variables
});Returns Promise<SendResult>:
interface SendResult {
messageId: string; // Unique message ID
accountUsed: string; // Account name that sent the email
provider: string; // 'gmail' | 'microsoft' | 'smtp' | ...
attempts: number; // Retry count (1 = first try)
rateLimitRemaining: {
day: number;
hour: number;
minute: number;
};
timestamp: string; // ISO 8601
}strapi.plugin('magic-mail').service('email-router').send(options)
Direct access to the Magic-Mail router. Same options as above plus additional flags.
await strapi.plugin('magic-mail').service('email-router').send({
...sameOptionsAsAbove,
skipRouting?: boolean, // Bypass routing rules (requires accountName)
skipRateLimit?: boolean, // Ignore rate limits (emergency only)
retryOnFailure?: boolean, // Default true
logToDatabase?: boolean, // Default true
});strapi.plugin('magic-mail').service('email-router').selectAccount(criteria)
Returns which account would be used for a given email (dry-run).
const account = await strapi.plugin('magic-mail').service('email-router').selectAccount({
to: 'user@example.com',
type: 'marketing',
subject: 'Hello',
});
// account = { accountName, provider, matchedRule, fallbacks: [...] }strapi.plugin('magic-mail').service('accounts').list()
Returns all configured accounts.
const accounts = await strapi.plugin('magic-mail').service('accounts').list();
// [{ id, name, provider, isActive, priority, stats: { sentToday, sentThisHour, ... } }, ...]strapi.plugin('magic-mail').service('templates').render(id, data)
Render a template to HTML without sending it.
const html = await strapi.plugin('magic-mail').service('templates').render(5, {
firstName: 'Alice',
orderTotal: '$42',
});Email types
Using type in your send call classifies the email and drives routing rules + compliance defaults:
| Type | Behavior |
|---|---|
transactional | Prioritized routing. No automatic unsubscribe header. |
marketing | Automatic List-Unsubscribe and List-Unsubscribe-Post headers. GDPR-safe defaults. |
notification | System notifications (password reset, 2FA). Highest priority routing. |
system | Internal system emails (admin alerts). Uses dedicated system accounts if available. |
Attachments
await strapi.plugin('email').service('email').send({
to: 'user@example.com',
subject: 'Invoice',
html: '<h1>Your invoice is attached</h1>',
attachments: [
{
filename: 'invoice-12345.pdf',
path: './uploads/invoice-12345.pdf', // From disk
},
{
filename: 'receipt.txt',
content: 'Thanks for your purchase!', // Inline content
},
{
filename: 'photo.jpg',
content: buffer, // Buffer
contentType: 'image/jpeg',
},
],
});Attachment support by provider:
| Provider | Support |
|---|---|
| Gmail | Full (up to 25 MB total) |
| Microsoft 365 | Full (up to 25 MB total) |
| Yahoo | Full (up to 25 MB total) |
| SMTP | Full (server-dependent) |
| SendGrid | Encoded in JSON body (up to 30 MB) |
| Mailgun | Limited (10 MB) |
Using templates
Templates are created and versioned in the Magic-Mail admin UI. Reference them by templateId:
await strapi.plugin('email').service('email').send({
to: 'user@example.com',
templateId: 42,
templateData: {
user: { firstName: 'Alice', lastName: 'Smith' },
order: { number: '12345', total: '$199.99' },
},
});Templates use Mustache syntax (e.g. {{user.firstName}}). The subject, HTML, and text are all templatized.
Error handling
try {
await strapi.plugin('email').service('email').send({ to, subject, html });
} catch (err) {
if (err.code === 'RATE_LIMITED') {
// All accounts in chain are rate-limited
} else if (err.code === 'NO_ACCOUNT_AVAILABLE') {
// No active accounts match the routing rule
} else if (err.code === 'AUTH_FAILED') {
// OAuth token expired, API key invalid, etc.
} else if (err.code === 'VALIDATION_ERROR') {
// Invalid email format, missing required fields
} else {
// Unexpected — check err.message and err.originalError
}
}All errors log to the Email Logs table with full context. See them in Admin → Magic-Mail → Email Logs.
Rate-limit inspection
const accounts = await strapi.plugin('magic-mail').service('accounts').list();
accounts.forEach(acc => {
console.log(`${acc.name}: ${acc.stats.sentToday}/${acc.limits.perDay} today`);
});REST API
Magic-Mail also exposes admin-only REST endpoints (authenticated Strapi admin token required):
| Method | Path | Purpose |
|---|---|---|
| GET | /magic-mail/accounts | List accounts |
| POST | /magic-mail/accounts | Create account |
| PUT | /magic-mail/accounts/:id | Update account |
| DELETE | /magic-mail/accounts/:id | Delete account |
| POST | /magic-mail/accounts/:id/test | Send a test email |
| GET | /magic-mail/routing-rules | List rules |
| POST | /magic-mail/routing-rules | Create rule |
| GET | /magic-mail/templates | List templates |
| GET | /magic-mail/logs | List email logs with filtering |
TypeScript types
For stronger typing, augment Strapi's plugin registry:
// types/magic-mail.d.ts
declare module '@strapi/strapi' {
interface Plugins {
'magic-mail': {
service(name: 'email-router'): {
send(options: SendOptions): Promise<SendResult>;
selectAccount(criteria: Partial<SendOptions>): Promise<AccountSelection>;
};
service(name: 'accounts'): {
list(): Promise<Account[]>;
};
service(name: 'templates'): {
render(id: number, data: Record<string, any>): Promise<string>;
};
};
}
}Next: Examples & Recipes →