Email deliverability
What keeps campaign emails out of the spam folder, and how Swarmhit handles bounces and complaints.
Email outreach lives or dies by the reputation of the sending address. Swarmhit paces sends, threads follow-ups and stops on bounces and complaints, but the domain and mailbox setup is yours. This page is the short checklist.
Before the first campaign
- Authenticate the sending domain. Publish SPF, DKIM and DMARC records for the domain of every connected mailbox. Google Workspace and Microsoft 365 document the exact records; without DKIM most providers file cold email as spam regardless of content. Swarmhit reads these records when a mailbox connects and once a day after that: the mailbox page shows each one with the fix when something is missing, and the mailbox list flags the domain. Over the API they are
dnsHealthon the mailbox, wherespf,dkimanddmarceach have astatusofok,missing,invalid,weak(an SPF record that lets any server send) orunknown. After changing DNS, use Check again on the mailbox page (orPOST /email-accounts/{id}/check-dns, which returns the mailbox with freshdnsHealth). DKIM keys live under a name the sender picks, so outside Google Workspace and Microsoft 365 a key we cannot find is reported asunknown, notmissing, and so is a lookup that failed. Addresses on consumer domains such as gmail.com need nothing: the provider publishes the records (dnsHealth.managedistrue). - Use a mailbox with history. A brand-new address has no reputation. Send real conversations from it for a couple of weeks before it joins a campaign pool, and keep its daily limit low at first. A mailbox bought through Swarmhit with warmup is kept out of campaigns until it has warmed for 14 days (and after that while warmup health is
warningorcritical) unless you choose to send anyway; see Mailboxes. - Keep the daily limit modest. Each mailbox has its own daily cap: 30 emails unless you change it in the mailbox settings in Swarmhit (up to 150), counted per UTC day. The API reads it but does not change it:
limits.emailson each mailbox inGET /email-accounts(limitsisnullwhile the default applies), andusage.emailscounts the sends of the UTC day inusage.day. Emails are paced 3 to 7 minutes apart per mailbox, at random, inside the campaign schedule, so a campaign never sends in bursts. - Write like a person. Campaign emails go out as plain text with light formatting. Short, specific, one question.
Threads and follow-ups
A follow-up email step with config.sameThread: true (the default) is sent as a reply under the campaign's first email to that lead (In-Reply-To and References headers, and the first email's subject with Re:), so the recipient sees one conversation. Replies land in the same thread in the inbox, and a reply on any channel ends the sequence.
Testing a mailbox
Run test on the mailbox page sends one ordinary email from the mailbox to our test inbox, the way a campaign email from it goes out (signature and the mailbox's own tracking settings included). Within a couple of minutes the page shows whether it landed in the inbox or in spam (or did not arrive within 10 minutes), whether SPF, DKIM and DMARC passed as the receiving server saw them, and whether the sending server or the domain is on a Spamhaus blocklist, with the fix for anything that failed. One test runs at a time per mailbox, and a mailbox can run 10 a day (per UTC day; a test whose email could not be sent does not count). Each test email counts toward your plan's emails like any other.
Over the API, start it with POST /email-accounts/{id}/deliverability-test and read deliverabilityTest on the mailbox, polling about once a minute: its status goes from running to done or failed. A done test has landing (inbox, spam, or missing when it did not arrive), the receiving server's spf, dkim.result and dmarc results, and blocklists.ip and blocklists.domain (clean, listed or unknown). A DKIM result of unaligned means the email was signed, but for another domain than yours (typically the provider's default key). A failed test says why in failure: send (the email could not be sent) or check (the result could not be read). The start call answers 409 while a test is already running or when the mailbox is paused or not connected, 429 once it has run 10 tests that day, 400 with code: email-limit-reached while email is paused for the workspace (its email allowance is used up or a payment failed), and 502 when the test email could not be sent.
It is one inbox at one provider, so treat a single spam result as a signal to check, not a verdict: fix what failed, then run it again.
Tracking opens and clicks
Each mailbox has two settings, changed in its settings in Swarmhit; the API reads them as trackOpens and trackClicks on the mailbox. Track opens adds one tiny image to its emails; it is on by default for a newly connected mailbox. Track link clicks sends every link through a tracking address first, which some spam filters hold against the sender, so it starts off. A campaign can decide for itself instead (Config tab, or tracking: { opens, clicks } on the campaign in the API, each mailbox, the default, to follow each mailbox, or on / off for all its emails). A campaign with an if_email_opened step tracks opens on its emails either way, and one with an if_email_clicked step tracks clicks.
- Opens are approximate. Apple Mail and some corporate filters load images on their own, so an open can be counted that nobody made; Outlook and other apps block images, so a real open can be missed. Treat them as a hint, and use them for a follow-up, not for anything stronger.
- Clicks are closer to the truth, but mail security scanners follow every link as the email lands. Opens and clicks in the first two minutes after a send are ignored for that reason, and so are those from obvious bots and scripts. A click on an open-tracked email also counts as its open.
- Rates are per email, against the emails sent with that tracking on, so a mailbox that tracked nothing shows no rate rather than 0%. Over the API, mailbox stats report that case as a
rates.openorrates.clickof0: checklifetime.emailsOpenTrackedandlifetime.emailsClickTrackedfirst.
Your own tracking domain
By default, the open image and tracked links use a shared tracking address. Any workspace with email can use its own domain instead, under Settings, Email: enter a subdomain such as track.yourdomain.com, add the one CNAME record shown, and it goes live by itself once the record resolves (minutes to an hour). From then on it carries the open image, tracked links and the unsubscribe link. A domain of your own keeps your links' reputation yours, and recipients only ever see your domain. An organization's tracking domain also covers its workspaces that have none. Emails keep the address they went out with, so removing or changing the domain breaks tracked links and unsubscribe links in emails already sent. The workspace in GET / reports it as trackingDomain: hostname, status, and inherited (true when the domain is the organization's).
Bounces and complaints
- Any bounce fails the lead if it is still on the step that sent the email (a delivery the server merely delays is not a bounce) and fires the
email.bouncedwebhook, whoseleadFailedsays whether it did. - A hard bounce (the address does not exist) also marks the address on the lead (
emailStatus: bounced,deadAddress: truein the webhook) unless the lead has replied since that email, so from then on the lead stops at any email step it reaches (route it withif_has_emailto avoid that). A soft bounce (mailbox full, greylisting) leaves the address usable. - A spam complaint marks the address as
complained, opts the lead out of email (emailOptOut: true), fails the lead the way a bounce does, and firesemail.bouncedwithkind: complaint.
Watch the mailbox counters and stats (GET /email-accounts/{id}/stats): a bounce rate above a few percent means the list is stale, a complaint means the copy or the targeting is off. Pause the mailbox (POST /email-accounts/{id}/pause) while you fix either.
Opting out
Each campaign has an unsubscribe line (Config tab, or unsubscribe: { enabled, text } on the campaign in the API): one sentence of up to 200 characters where the words in [brackets] become the link (the whole line when there are none), Not interested? [Let me know] by default. Write it in the language of your emails. The link opens a short page that asks once and then unsubscribes the person.
-
Add to every email (
enabled, off by default) puts the line after the signature of every email the campaign sends. -
{{unsubscribeLink}}in an email step's message puts the line exactly there instead (it works in the fallback message too, and never makes a fallback required). If a lead gets a fallback that leaves it out, the line still goes after the signature. -
An email that carries the line also gets a one-click
List-Unsubscribeheader, so Gmail and other mail apps can show their own Unsubscribe button next to the sender. With the switch off and no{{unsubscribeLink}}, emails go out exactly as before. -
Unsubscribing opts the address out for the whole workspace (
emailOptOut: trueon every lead with it), so no email step sends to it again, and stops the lead in the campaign that emailed it. LinkedIn steps in other campaigns are not affected. -
Opening the link does nothing on its own: mail security scanners open every link as an email lands, so only the page's button or the mail app's one-click unsubscribe counts. A click on the link is not an open or a click in the stats.
-
Each unsubscribe shows in the lead's activity and fires the
email.unsubscribedwebhook, whosesourceispageorone-click.
A reply asking to stop ends the sequence like any reply, and a spam complaint opts the lead out automatically, link or not.