Docs · Guides

Sending and deliverability

How campaign email leaves MagpieCRM, what happens when it bounces, and what keeps your domain in good standing. Cloud and self-hosted copies differ in where mail is sent from, so each has its own section.

Checked against magpiecrm@0.9.23 · Updated

On Magpie Cloud: sending domains

On Magpie Cloud, email goes out through our sending servers, signed for your own domain. There's no provider account to set up. You add the domains you send from:

  1. Open Settings, then Sending, and add your domain.
  2. Copy the records shown into your DNS: the DKIM records, an ownership TXT record and, ideally, a DMARC record.
  3. Wait for the domain to show as ready. Mail only goes out from domains that are ready.

If your domain already has a DMARC record, keep yours. Bounces and complaints are recorded for you, and every campaign carries an unsubscribe link. See Start on Magpie Cloud.

Self-hosted: choose a provider

A self-hosted copy sends through your own provider. Open Settings, then Sending, pick one of the nine and enter its credentials:

  • Amazon SES: region, access key and secret. Add a configuration set if you want bounce events.
  • Postmark: server token and message stream. Use a broadcast stream for marketing.
  • Resend, SendGrid, Brevo and Mailchimp Transactional (formerly Mandrill): an API key.
  • Mailgun: API key, sending domain and region (US or EU, matching where the domain was created).
  • Cloudflare: account ID, API token and, for bounce collection, a zone ID.
  • Generic SMTP: host, port, username and password. It needs outbound SMTP ports, which many hosts block; the others all send over HTTPS.

On the same page, set the Default sender, used when a message doesn't name its own, and use Send a test email. Add every address you send from under Settings, then Sender addresses. Provider settings can also come from environment variables; see Environment variables.

No provider, no email

Until a provider is set up with all its required fields, email is written to the server log, not sent.

Bounces and complaints

Set WEBHOOK_SECRET first: until it's set, the webhooks refuse every request. Then point your provider's bounce and spam complaint events at:

Webhook address
https://crm.example.com/api/webhooks/email/<provider>?s=<WEBHOOK_SECRET>

# or send the secret as a header instead of ?s=
Authorization: Bearer <WEBHOOK_SECRET>
  • Resend, Postmark, SendGrid, Mailgun, Brevo and Mailchimp Transactional: add both the bounce and the spam complaint events. Use mailchimp as the provider in the address for Mailchimp Transactional.
  • Amazon SES: have your configuration set publish Bounce and Complaint events to an SNS topic with an HTTPS subscription to /api/webhooks/email/ses?s=…. The app confirms the subscription itself.
  • Cloudflare: bounces are collected by polling, so there's no webhook. The API token needs Analytics: Read, and the zone ID must be set.
  • Generic SMTP: SMTP can't report bounces back. The repository includes a Cloudflare Email Worker in cloudflare-worker/ that reads bounce messages and forwards them to /api/webhooks/bounce. Give it BOUNCE_WEBHOOK_URL and the same WEBHOOK_SECRET. Or use a provider with webhooks.

What each event does:

  • A hard bounce (the address doesn't exist, including a "no such user" refusal at send time) marks the contact bounced. A spam complaint marks them unsubscribed. Either way they're left out of every campaign and welcome email, and one-off emails to them are refused.
  • A soft bounce (a full mailbox, a busy server) only counts against that campaign. The next one tries again.

These are also remembered as a keyed hash of the address, so a contact who's deleted and imported again comes back bounced or unsubscribed. Re-subscribing them, or them signing up again, lifts it.

Unsubscribes

Campaign emails carry a personal unsubscribe link, added at the bottom unless your design already includes {{unsubscribe}}. They also carry one-click List-Unsubscribe headers, which Gmail and Yahoo require of bulk senders. The unsubscribe page asks people to confirm, and links back to your website if PUBLIC_SITE_URL is set.

The link is on by default. A self-hosted copy can switch it off per campaign with Include Unsubscribe Link; on Magpie Cloud it's always on.

Open and click tracking

Links are rewritten so clicks are counted, and a tracking pixel counts opens when the campaign has Track opens on (the default; check whether you need consent in the UK and EU). MagpieCRM turns off the provider's own tracking so links aren't wrapped twice.

Business mail is often scanned on arrival, and scanners follow every link. A click or open is treated as automated when it says it's a scanner, comes within seconds of sending, comes just after the email's hidden link (which no person can see) was followed, or (for clicks) arrives in a burst across several links. The request's user agent isn't stored. Apple Mail loads images as mail arrives, so treat open rates as a guide.

Unconfirmed addresses go out in a first batch

Some prospected addresses can't be confirmed by the company's mail server, at catch-all domains for example. When a campaign includes them, confirmed addresses go out as usual along with a first batch of up to 50 unconfirmed ones. The rest are held for at least an hour while bounces come in. If more than 2% of the first batch hard-bounce, the rest stay held; otherwise they're sent. Held addresses can still be sent with Send anyway.

SPF, DKIM and DMARC when self-hosting

A self-hosted copy has no DNS tooling of its own. Authenticate your sending domain at your provider, following its instructions for SPF and DKIM, and add a DMARC record, before you send a campaign. Each provider authenticates your domain separately, so switching provider means doing it again. Without it, your mail will land in spam.

Something wrong or missing? Email pele@magpiecrm.com or open an issue.