Emails
Send transactional email from your site, from its own address or a domain of yours, with a monthly allowance, free test emails, delivery status for every recipient, open and click tracking, a history kept until you clear it, and forwarding of the mail your site receives to the addresses you confirm.
On this page
Every site can send email, and sending is on from the start. A contact form notification, an order receipt, a password reset link: your site calls a helper, and Modulify hands the message to a mail provider from an address that belongs to that site. There is no mail service to sign up for and no API key to paste in, and nothing to set up in DNS unless you want the site to send from a domain of your own, see Use your own domain.
Mail sent to your site reaches the addresses you forward it to, starting with the workspace owner's account email. Any address at the site's built-in domain accepts mail, and so does any address at a domain of yours once you turn on receiving for it. Forwarding is on from the start as well, see Forwarding.
Before you begin
Sending is on for every site unless somebody turns it off. The site gets what its code needs to send on its own, the next time its preview starts and every time it is published, so nobody has to open the Emails tab first. While sending is off, every send your site attempts is refused straight away and nothing is queued, so the message is lost rather than delayed.
Email belongs to a project. Any member of the workspace that owns it can turn sending on and off, change the sender, turn open and click tracking on and off, send a test, block an address, show and copy the key, export the history and read everything in the tab, which also checks every email domain that is not settled yet while it is open. Adding and removing email domains, choosing the domain the site sends from, restarting a domain's verification and one-click setup at a DNS host need the Manage domains permission, and so does changing the sending address while one of your domains is chosen as the sending domain. Rotating the key, clearing the history, removing one email from it, unblocking an address, turning forwarding on or off and turning receiving on or off for an email domain need the Delete projects permission. Without the permission a control needs, it is disabled, with the tooltip "You do not have permission to do this."
Nothing is sent on its own. Your site's code sends, so "email me when the form is submitted" is a change to the site, made in chat like any other. The one exception is a single email you ask chat to send for you, covered in What the AI can do.
Open the Emails tab
Click More in the tab strip, then Emails. The address ends in /emails.
Four tabs sit under the heading, and each control and value lives on exactly one of them, except the note shown on both Emails and Settings while the domain the site sends from is not working or paused:
- Emails, the switch, who the site sends as, a free test send and this month's usage
- Insights, the counts and charts for the period you pick, split into Sending, Opens and clicks and Credits
- History, the same three as full lists with filters: every message the site sent, the tracked emails with their opens and clicks, and every credit charge for extra emails
- Settings, the sender, the email domains, forwarding, open and click tracking, API access with the email key and a code example, the blocked addresses and the danger zone
Every plan sees the whole tab and every control. On Free the This month card carries an orange Paid badge, because sending past the included emails needs a paid plan, and Email domains carries one too, because adding a domain of your own needs a paid plan.
Turn sending off and on
Sending on the Emails tab opens with "Sending is on for every site unless someone turns it off." Its Send email from this site switch turns sending on or off for the whole site.
While sending is on, the site holds two managed secrets, EMAIL_URL and EMAIL_PRIVATE_KEY. Modulify creates them the next time the preview starts, and every publish hands them to the live site, so a site sends from its preview and its live address without anybody touching this tab. A live site published before email was set up for it picks the key up on its next publish. When the key changes after a publish, the live site's sends fail until you publish again. A subdomain change does the same to a live site that was published while EMAIL_URL pointed at the site's CDN address, because that address moves with the subdomain and the old one stops answering. A live site published while EMAIL_URL still held the direct Modulify API address keeps sending, since that address keeps working.
Turning it off asks first, Turn off email sending?, because your site stops sending immediately. It keeps everything else: the address, the settings, the blocked addresses and the history, and forwarding carries on. While it is off a send is refused with Email sending is turned off for this site!, so a form or a receipt that depends on it quietly stops working. Under the switch the row says who turned it off, for example Turned off from the chat. or Turned off from a connected MCP client.
Turning it back on sends nothing by itself. It creates the two secrets at once if the site has none yet and hands both to the running preview, so the preview can send straight away. The toast reads Emails ready with Your site sends from followed by the sending address.
When Modulify switched sending off because of bounces or spam reports, the switch asks you to confirm before it turns back on. See Automatic switch off.
A site that already has its own secret named EMAIL_URL or EMAIL_PRIVATE_KEY, left from a mail service it used before those names were reserved, is the one exception. Modulify never writes over that secret, so it keeps reaching your site exactly as before, and the site cannot send through Modulify until you rename or delete it in Secrets. The tab says so in an alert, Your own secret keeps sending off: This site has its own EMAIL_URL secret, so email stays off until that secret is renamed or deleted in Secrets!, with an Open Secrets button. Until then the switch, Send a test email and the icons beside the key are disabled, with the tooltip Rename or delete your own EMAIL_URL secret in Secrets first. Once the secret is renamed or deleted, the site gets its own email key, which reaches the preview straight away while sending is on and the live site on its next publish.
If the Emails tab shows Email sending is not set up, with "Your site cannot send until the platform is configured, so every control here stays disabled except turning sending off.", sending is not available on the platform at that moment, and every control on every tab is disabled, Remove from history included, except turning sending off, turning a domain's receiving off while it is on, Remove domain in an email domain's menu, and everything under Forwarding, which depends only on whether forwarding itself is set up, see Turn forwarding off and on.
Who your email comes from
Every site has a built-in address on its own subdomain, like [email protected], and sends from it until you give it a domain of your own, such as [email protected], see Use your own domain. The site's name sits beside the address. The Sends as row on the Emails tab shows both together, for example Lisbon Pottery <[email protected]>, and its Edit button opens the Settings tab, the only place they are changed.
The prj- part is the site's permanent id. It is not the free web address, so renaming the site's subdomain does not change the built-in address, and neither does connecting a custom domain: a domain the site is published on only becomes a sending domain once you add it under Email domains as well. Renaming the subdomain does move the site's CDN address, so a published site whose EMAIL_URL points at that address has to be published again to keep sending, see Your web address. The built-in address keeps working whichever domain the site sends from.
Sender on the Settings tab holds the three fields that shape what a recipient sees, each with its own Save button.
- Sender name is the name shown beside the address in an inbox, up to 64 characters. It starts as the site's name and keeps following it, so renaming the site renames the sender too. Type another name and save it to use that instead. Saving the field empty, or saving the site's name, goes back to following the site's name. A site without a name sends as
Notifications. - Sending address is the part in front of the
@, for examplehelloorordersinstead ofnoreply, the default. It takes 1 to 32 lowercase letters, numbers, dashes, underscores or single dots, starting and ending with a letter or number. The domain the site sends from sits beside the field, and once one of your email domains is verified it becomes a menu of the built-in domain and every verified domain, which saves the moment you pick one, see Choose the domain the site sends from. Changing either part changes the address every later message comes from, which restarts the recognition mailboxes have built up for it, so pick one early. While one of your domains is chosen as the sending domain, even while it is Not working and mail goes out from the built-in address, changing the part in front of the@needs the Manage domains permission, and without it the field is disabled with the tooltip "Changing the sending address on your own domain needs the Manage domains permission." - Reply-to address is where an answer goes. Without one, a reply goes to the sending address. On the built-in domain, and on one of your domains with receiving on, forwarding passes it on to the site's forwarding addresses while it is on. On one of your domains without receiving, it lands in that domain's own mailboxes, see Where replies go. Set one when somebody else should get the answers, such as a shared support mailbox, and save the field empty to clear it.
A value that does not fit is refused, never shortened. A saved change shows Email settings saved. A single message can still set its own sender name and reply-to from code, but never its own address: every email goes out from the address in Sends as.
Use your own domain
A site can send from a domain you own, such as [email protected], instead of its built-in address, and receive the mail sent to that domain too. Email domains on the Settings tab, right under Sender, holds them: "Send and receive email at a domain you own." It lists the site's built-in domain first, marked Built-in, which keeps working next to them, then every domain you added, up to 3 for each site. Any domain or subdomain works, as long as you can add DNS records for it.
Adding a domain needs a paid plan and the Manage domains permission. On Free the section carries a Paid badge, and Connect is disabled with the tooltip "Adding your own email domain requires a paid plan." It is also disabled without the permission, with "You do not have permission to do this.", once the site has 3 domains, with "A site can have up to 3 email domains.", and while sending is not set up on the platform, with Email sending is not set up on this server yet. Using a domain needs the paid plan as well: once the plan ends, the domains you already added are paused until the workspace is on a paid plan again, see When your plan ends.
Add the domain
Connect a domain sits at the top of Email domains, with a field for the domain and the line "A subdomain like mail.example.com keeps your current mailboxes." Use a domain you can add DNS records for. When the site has a custom domain that is not on the list yet and that no other site uses for email, the field starts with it filled in, and Recommended: example.com is this site's custom domain. sits under it. With several custom domains, that is the primary domain without its www., or the first domain you added while none is connected yet.
Type the domain without www., such as example.com or mail.example.com. Leave Send from this domain once it is verified ticked if the site should switch to the domain on its own, see Choose the domain the site sends from. Then click Connect, or press Enter.
Add the records at your DNS host
The domain joins the list as Pending, with its DNS records under it and the line Add these records at the DNS host that manages example.com. DNS changes can take a few minutes to propagate. That is whoever runs the nameservers of the domain today, which is not always the company you bought it from. Click any name or value to copy it. The records to add explains each one.
When that DNS host supports one-click setup, a button such as Connect with Cloudflare sits above the records, with the line "One step at Cloudflare adds the ownership and sending records below." It shows even before the host has switched one-click setup on for Modulify, and until it has, clicking it shows One-click setup not ready yet and opens nothing. Once it has, it opens your DNS host in a popup, where you approve the ownership record and the three sending records, and Modulify checks the domain again once the popup closes. If another site's ownership record is at that name, it is replaced by this site's, which moves the domain here once it verifies. The DMARC record, and the receiving MX record, are never added this way. The button needs the Manage domains permission and shows only while a required record is still missing.
Wait for Verified
Modulify checks the records on its own, every 15 seconds while the Settings tab is open, and in the background every few minutes at first and less often as the days pass, so you can close the tab. Once every required record is found, and while the mail provider is still checking them, the row reads "All records found. Waiting for the mail provider to confirm, which can take up to 72 hours." The domain turns Verified once the mail provider confirms it, and from then on the site can send from it. When the mail provider has stopped checking instead, for example because the sending records were not in place within 72 hours, the row reads "The mail provider stopped checking this domain. Use Restart verification, then replace the three sending records with the new ones.", see Restart verification.
A domain starting with www. is refused with Use the domain without www, such as example.com!, and an address Modulify itself uses with You cannot use a Modulify domain as an email domain! A workspace can add up to 10 email domains an hour and 30 a day across all its sites. Every add that gets past those checks, the paid plan and the limit of 3 counts, including one that then fails because the mail provider is busy and a domain removed since, and an add past that is refused with how long to wait, such as This workspace has added too many email domains this hour. Try again in 42 minutes! A domain still not verified 14 days after it was added, after it was last checked while the Settings tab was open, or after its last Restart verification, is removed from the list on its own. Adding it again starts over, with a new ownership record.
The records to add
Names are shown relative to the zone, the way most DNS hosts expect them, so a record at the domain itself reads @. A subdomain's records go in the zone of the domain it belongs to: for mail.example.com they go wherever example.com is managed, and their names end in mail.
| Purpose | Type | Name for example.com |
Name for mail.example.com |
Value |
|---|---|---|---|---|
| Ownership | TXT | _modulify-email |
_modulify-email.mail |
modulify-email-verify= followed by this site's own code |
| Sending, three records | CNAME | <code>._domainkey |
<code>._domainkey.mail |
Shown in the tab, a different one for each record |
| Receiving, only while receiving is on | MX | @ |
mail |
The mail server shown in the tab, with priority 10 |
| DMARC | TXT | _dmarc |
_dmarc.mail |
v=DMARC1; p=none; |
- Ownership proves the domain is yours. Its code belongs to this site alone, so another site adding the same domain gets a different one. Keep the record in place after the domain is verified, because a domain that stops working for more than a day needs it found again, see When a domain stops working. Add it as a TXT record at exactly that name: a value reached through a CNAME record at that name, a wildcard one included, does not count.
- Sending is three CNAME records, known as DKIM, that let the mail provider sign every email from the domain, which is how mailboxes know the mail really comes from it. Their names and values come from the mail provider, so copy each one exactly.
- Receiving is listed only once you turn receiving on, see Receive mail at your domain.
- DMARC is marked Recommended and is never required. It tells mailboxes how to treat mail that fails those checks. Any DMARC record already at that name counts, so keep yours if you have one. For a subdomain, a DMARC record on the domain it belongs to covers it too, and the row then reads Inherited with nothing to add.
The ownership record and the three sending records are required: the domain is verified once its ownership record is found and the mail provider confirms the three sending records. Each record carries a status:
| Status | What it means |
|---|---|
| Valid | The record is in place with the right value |
| Pending | Not found yet, which is normal right after you add it |
| Invalid | Something other than the value shown is at that name, and the row shows what was found |
| Not added | Nothing is at that name. That is fine for the DMARC record, and for the ownership record of a verified domain it means the record was deleted |
| Another provider | The domain's MX record points at another mail server, which the row names |
| Inherited | The DMARC record of the domain it belongs to covers it |
| Unknown | The lookup itself did not come back, so nothing is known either way |
A domain has a status of its own as well:
| Status | What it means |
|---|---|
| Pending | Not verified on this site yet. Email keeps going out from the address the site uses now |
| Verified | Its ownership record was found and the mail provider confirmed its sending records, so the site can send from it |
| Not working | It was verified, then its sending records or the mail provider stopped confirming it, see When a domain stops working |
| Paused | The workspace's paid plan ended, so the domain neither sends nor receives until the workspace is on a paid plan again, see When your plan ends |
The records of a domain that is not verified are always open under it, and a verified domain keeps them behind a DNS records toggle. A Sending badge marks your own domain while the site sends from it, and the built-in domain always shows Built-in and Connected.
Choose the domain the site sends from
With Send from this domain once it is verified ticked when you add a domain, the site switches to it on its own the first time it is verified, as long as no domain of yours is chosen as the sending domain then. Untick it to switch when you are ready.
To switch by hand, pick the domain in the menu beside Sending address in Sender, which lists the built-in domain and every verified domain with the full address each one gives, or choose Send from this domain in the domain's own menu, which is disabled with "Verify the domain first." until it is verified. Pick the built-in domain in the same menu to go back. A chosen domain that stops working stays in that menu, disabled and marked Not working, and a paused one stays disabled and marked Paused. Either way the change saves at once, needs the Manage domains permission and works on any plan, except that a paused domain cannot be chosen. The part in front of the @ stays the same, so [email protected] becomes [email protected].
The site sends from one domain at a time, and no single email can pick another address, whether it comes from your site's code, from chat or from a connected AI client.
Where replies go
A reply goes to the reply-to address when one is set. Without one it goes to the sending address. On the built-in domain, or on one of your domains with receiving on, Modulify forwards it to the site's forwarding addresses while forwarding is on. On one of your domains without receiving, the reply reaches that domain's own mailboxes, wherever its MX record points today, and never passes through Modulify, unless that MX record already points at Modulify: then the reply is discarded until you turn on receiving, see Receive mail at your domain. The add dialog says so under the checkbox, "Replies go to example.com mailboxes unless you set a reply-to or receive its mail here.", and Sender says the same once the site sends from your domain.
Receive mail at your domain
Once a domain is Verified, its row gains Receive mail: "Mail sent to any address at example.com is forwarded to the site's forwarding addresses while forwarding is on." Its switch needs the Delete projects permission, like Forward incoming mail, and without it the switch is disabled with "You do not have permission to do this." While forwarding is not set up on the platform it cannot be turned on, and its tooltip reads Email forwarding is not set up on this server yet. While the domain is paused, the switch is disabled with "Paused until this workspace is on a paid plan again."
Turning it on asks first, Turn on receiving?: "Every message sent to any address at example.com is forwarded to the site's forwarding addresses. Point the MX record here only if you want its mail moved from its current mailboxes." The records then gain a Receiving row, an MX record for the domain itself with priority 10, pointing at the mail server shown in the tab. Mail reaches Modulify only once that record is in place. From then on, mail to any address at exactly that domain is handled like mail to the built-in domain: the same Forward incoming mail switch, limits and checks apply. Mail to a subdomain of it, such as news.example.com when you added example.com, is not included.
While the domain's MX record still points somewhere else, the Receiving row reads Another provider and names where its mail goes now, for example "This domain receives mail at mx1.example.net. Point its MX record here to move its mail to forwarding."
A domain that is itself a CNAME works differently, because a name that holds a CNAME record cannot hold an MX record as well. A site connected at a subdomain such as blog.example.com usually has one there, since that is the record that connects it. Mail sent to such a domain follows its CNAME instead, and the Receiving row names where the CNAME points, for example "Found: via cname.modulify.website". When that row reads Valid, the domain's mail already reaches Modulify and there is no MX record to add. Otherwise, never delete the CNAME to make room for the MX record, because the site goes offline without it. Add another subdomain such as mail.example.com as an email domain and receive there instead.
A domain's MX records decide where every email to it goes. Pointing them at Modulify moves the mail for every address at the domain away from the mailboxes that get it today, such as a Google Workspace or Microsoft 365 inbox, and into forwarding. To keep those mailboxes, add a subdomain such as mail.example.com for the site and receive there instead.
Turning receiving off asks first, Turn off receiving?: "Mail sent to example.com, including mail still waiting to be forwarded, stops reaching the site's forwarding addresses. While its MX record points here, that mail is discarded and never forwarded later." It then stops the forwarding at once, and the Receiving row leaves the records. Mail that still arrives because the MX record points here is no longer forwarded, so point the MX record back at your mailboxes when you turn receiving off. While a verified domain's mail still reaches Modulify with receiving off, its row says so: "Mail sent to this domain already reaches Modulify, but receiving is off, so it is discarded. Turn on Receive mail to forward it to the site's forwarding addresses."
Turning receiving on is refused while one of the site's forwarding addresses is at that domain, with A forwarding address of this site is at this domain, so its mail would come straight back. Remove that address first!, see Forwarding addresses.
One site per domain
A domain sends and receives for one site at a time. When another site already uses it, adding it still works, with Email domain added and "It moves to this site once its ownership record is verified." Its row stays Pending and names the other site when it is in the same workspace, "Used by Lisbon Pottery. Verifying it here moves it: Lisbon Pottery stops sending from it and receiving its mail, and receiving starts off here.", or reads "Used by another site. Verifying it here moves it to this site, with receiving off."
The domain moves once this site's ownership record is found while the domain is verified with the mail provider. Each site gets its own ownership code, so add the new TXT value next to the old one at the same name, and delete the old one once the domain has moved. The three sending records stay as they are. The site the domain moved from loses it from its list, goes back to its built-in address if it sent from the domain, and stops receiving its mail. Receiving does not move with the domain, so turn it on again on the new site.
Moving or transferring a site to another workspace takes its email domains away from it, and it goes back to its built-in address. Someone in the new workspace adds them again, with new ownership records to prove the domains are theirs, and their sending records can change too, so copy them from the table again.
When a site is deleted, its email domains stop with it. It sends nothing, and mail to a domain it received for is no longer forwarded, so point that domain's MX record back at your mailboxes. Adding one of its domains to another site moves the domain there once its ownership record is verified.
Remove a domain
Remove domain in the domain's menu asks first, Remove this email domain?: "If the site sends from this domain, sending goes back to the built-in address. While its MX record still points here, mail sent to it is no longer forwarded." It needs the Manage domains permission and works on any plan. It takes effect at once: the domain leaves the list, the site sends from its built-in address if it sent from this domain, and mail to the domain is no longer forwarded, including mail that arrived but was not forwarded yet. You can then delete its records at your DNS host. Adding the domain again later starts over with a new ownership record, and its sending records can change too, so copy them from the table again.
Restart verification
Restart verification in the domain's menu sets the domain up again with the mail provider when the verification of its sending records failed or never started, for example because the records were not found in time. Otherwise it is disabled with "Only needed when the sending records fail to verify.", and it is refused with This domain is verified, so there is nothing to restart! while the domain is verified on any site, with The mail provider already confirmed this domain's sending records, so there is nothing to restart! once the mail provider has confirmed them, in which case check that the ownership record is in place instead, and with The mail provider is still verifying this domain, so there is nothing to restart! while the mail provider is still checking its records. It asks first, Restart verification?: "The three DKIM records will change. Replace them at your DNS provider after restarting." So replace the three sending records with the new values the table then shows. The ownership record stays the same. It needs the Manage domains permission. A workspace can restart verification up to 10 times an hour, and past that it is refused with how long to wait, such as This workspace has restarted email domain verification too many times this hour. Try again in 42 minutes! While the domain is paused, it is disabled with "Paused until this workspace is on a paid plan again." and refused with Email domains are paused until the workspace is on a paid plan again!
When a domain stops working
Modulify checks a verified domain again every few hours. When the mail provider stops confirming its sending records, for example because they were deleted, the domain turns Not working, and until it is Verified again:
- Email goes out from the built-in address, so nothing your site sends is lost. Sending address says "Your domain is not verified right now, so email goes out from [email protected].", and the Emails tab shows the same line under Sending from the built-in address, with an Open settings button.
- Mail sent to the domain is not forwarded, even with receiving on.
Put the records back the way the table shows them. While the Settings tab is open, Modulify checks the domain every 15 seconds. When the row says the mail provider stopped checking this domain, use Restart verification first. A domain that has been Not working for more than a day turns Verified again only once its ownership record is found too, and its row says "This domain stopped working over a day ago. Add the ownership record again so it can be verified." That is why the ownership record stays in place after verification.
If the mail provider refuses an email because the domain is no longer verified, Modulify sends that email again from the built-in address straight away, so it still goes out, and checks the domain.
When your plan ends
An email domain needs a paid plan to work, not only to be added. When the workspace's plan ends and the workspace is on Free, the email domains of its sites are paused. A payment that failed and is still being retried pauses nothing, and neither does a cancellation that is only scheduled. While they are paused:
- Email domains shows Email domains are paused above the list, "They neither send nor receive until this workspace is on a paid plan again.", with a See plans button.
- Every domain stays on the list, with a Paused badge in place of its status and without its Sending badge, its notes and its records.
- Email goes out from the built-in address, so nothing your site sends is lost. While one of your domains is the chosen sending domain, Sending address says "Your email domains are paused, so email goes out from [email protected] until this workspace is on a paid plan again.", and the Emails tab shows the same line under Sending from the built-in address, with an Open settings button.
- Mail sent to the domains is not forwarded, including mail that arrived but was not forwarded yet.
- Send from this domain, Restart verification and the Receive mail switch are disabled with "Paused until this workspace is on a paid plan again." Choosing a paused domain as the sending domain or restarting its verification, from chat or a connected AI client too, is refused with
Email domains are paused until the workspace is on a paid plan again!Remove domain still works. - Modulify stops checking the domains, so a paused domain is not verified and cannot take a domain over from another site.
Nothing is deleted, and the chosen sending domain stays chosen. Once the workspace is on a paid plan again, the domains come back on their own: email goes out from the chosen domain again, mail to a domain with receiving on is forwarded again, and Modulify checks every domain again within a few minutes.
Send a test email
Send a test email on the Emails tab sends one email to your own account address and nowhere else. It asks first, Send a test email?: "It goes to your own account address and does not use any of this month's sends." It is disabled while sending is off, with the tooltip Turn sending on to send a test email.
A test email is free. It uses none of this month's sends, never unlocks an extra block, does not count towards the per minute limit, and still goes out on Free after the month's included emails are used up. It shows in the History tab marked Test, and Insights leaves it out. It is still refused while sending is off or not set up, and when your own address, or its domain, is on the site's blocked list.
The one limit is a pause of 10 seconds between two test emails from the same site. A sent test shows Test email sent, and a refused one shows Could not send the test email with the reason, for example Wait 7 seconds before sending another test email!
The email is a short, plain notice with the subject Test email from followed by the site's name, a text part and a simple HTML part, and no images or links. For a site called Lisbon Pottery it opens with "This is a test email from Lisbon Pottery, and it shows that the site can send email.", names the address it was sent from, and says where replies go: to the reply-to address when one is set, otherwise "Replies to it are forwarded to the site's confirmed forwarding addresses while forwarding is on.", or, when the site sends from one of your domains without receiving its mail, "Replies to it go to the mailboxes of example.com.", or "Replies to it are discarded, because receiving is off for example.com while its mail already reaches Modulify." when that domain's MX record already points at Modulify. It ends with "This test did not use any of this month's sends."
Send from your site
Sites built from the Modulify starter ship with a server only helper at src/lib/modulify/email. It reads EMAIL_URL and EMAIL_PRIVATE_KEY from the environment, so there is nothing to wire up. API access on the Settings tab shows the API URL, the key and a code example to copy.
import type { NextApiRequest, NextApiResponse } from 'next'
import { sendEmail, EmailError } from '@/lib/modulify/email'
const Handler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
const { submissionId, email, message } = req.body
const result = await sendEmail({
to: 'Studio <[email protected]>',
replyTo: email,
subject: 'New enquiry from your website',
text: message,
idempotencyKey: `contact-${submissionId}`
})
return res.status(200).json({ success: true, id: result.id, remaining: result.remaining })
}
catch (error) {
if (error instanceof EmailError) return res.status(502).json({ success: false, message: error.message, retryable: error.retryable })
return res.status(500).json({ success: false })
}
}
export default HandlerThe helper has five exports.
| Export | What it does |
|---|---|
sendEmail(options) |
Sends one message and resolves with { id, messageId, accepted, suppressed, remaining, reason, duplicate } |
getEmail(id) |
Reads one message this site sent, with every recipient's status, the delivery timeline, the tags, the attachment names, the stored body and, when it was tracked, its opens, clicks and clicked links |
listEmails({ limit, cursor, status, recipient, engagement }) |
Lists the site's messages newest first, 20 to a page by default and 100 at most, with nextCursor for the next page |
getEmailQuota() |
Reads this month's usage, whether sending is on and available, whether open and click tracking is on and set up, the sending address and every limit |
EmailError |
What a refused call throws, carrying status, code, data, retryable, retryAfter and idempotencyKey |
sendEmail takes to, cc, bcc, subject, html, text, replyTo, fromName, attachments, headers, tags and idempotencyKey. to, cc, bcc and replyTo take one address or a list of them, bare or as Name <address>, and a message needs at least one to address and at least one of html and text. Attachment content can be a base64 string, a Buffer, a Uint8Array or an ArrayBuffer. An option name the helper does not know throws instead of being ignored, so a misspelt replyTo never quietly sends a message whose replies go somewhere you did not intend. Every field and every rule is in Email HTTP API.
sendEmail resolves as soon as the mail provider accepts the message, which means sent, not delivered. Read what happened next with getEmail, or in the History tab.
Branch on error.code, not on the message. retryable is true for every answer that can succeed later unchanged: the per minute limit, a mail provider that was busy, paused or out of reach, sending that is briefly unavailable, extra emails that could not be paid for right now, a request that timed out on its way to Modulify and may already have sent, and an idempotency key whose first send is still going out. Queue those and try again. Everything else needs a changed message or somebody in the Emails tab. Pass an idempotencyKey, such as the form submission's id, or retry with error.idempotencyKey, the key the failed call used, and a retry of the same email never sends it twice.
Import it only from an API route, getServerSideProps or other server code. It throws in the browser by design, because the key must never reach a visitor. While sending is on, EMAIL_URL and EMAIL_PRIVATE_KEY reach the preview the next time it starts and the live site on its next publish. They also arrive at once when sending is turned on, or when the key is shown, copied or rotated, even while sending is off. Until then every call throws a plain Error saying email is not configured for this environment. Once the two values are there, a call made while sending is off throws an EmailError with status 403 and code disabled instead.
A site created before the helper could do all of this may hold an older copy of it, or none at all. Ask chat to add or update it: chat fetches the current helper and writes it into the project unchanged.
You rarely write any of this by hand. Describe what should be emailed in chat and the AI writes the route and points the form at it. For calling the endpoints from outside the site, or from another language, see Email HTTP API.
Use your own mail service
The built-in email is the default, and a site can send through a mail service of your own instead, such as Resend, SendGrid, Postmark, Mailgun or the SMTP details of a mailbox you already have. It is also how a site sends a newsletter or other bulk mail, which the built-in email is not for, and how it sends from your own domain on a free workspace, where adding an email domain is not open to you. Ask for it in chat:
Send the contact form emails through Resend.The first time you ask in a conversation, chat recommends the built-in email and says once what yours involves, then asks you to pick between your service, for example Use Resend, and Use built-in email. Pick yours and it builds it in its next turn. It skips the question and builds straight away when your message already shows you know about the built-in email, for example "use Resend anyway", when that service's key is already in Secrets, and when you want something the built-in email cannot send for you, such as a newsletter, or mail from your own domain while the workspace is on Free. In Plan mode, approving the plan is your answer.
- The key. It goes in Secrets, never in the code or the chat. If you paste it into chat anyway, chat stores it there for you. When it is not there yet, chat adds it holding the placeholder
REPLACE_ME, and does the same for the address to send from when you have not named one. Paste your real values over the placeholders. A key that can only send is enough, unless the feature also keeps a mailing list with that service, such as newsletter signups added to its contacts. - The address it sends from. It has to be on a domain that belongs to you or to the business the site is for, verified with that service, or, when you send through the SMTP details of a mailbox, that mailbox's own address. To test before a domain is verified, ask chat to use the test sender your service provides, which only reaches your own account's address. It can never be the site's
prj-sending address or an address on your free.modulify.websiteweb address. - Until the values are in, the feature keeps working and simply sends nothing, so your visitors never see an error. It sends from the preview once your real values are saved and the domain it sends from is verified with that service, and from the live site after your next publish. Publishing before then leaves the live feature sending nothing.
- What moves. Chat moves only what you asked it to move, so "just for this form" leaves your other emails on the built-in email. When your site sends all its mail through your service, new emails go there too.
- Who it mails. Chat only builds mail to people who gave your site their address, people your signed-in users choose to send to, and addresses you name for your own notifications, and a newsletter it builds carries a working unsubscribe link in every email.
- Where you see it. Mail sent through your service shows in that service's own dashboard, not in this tab. Chat cannot tell you whether one of those emails was delivered, and it never sends one itself: you test it by using the feature in the preview.
- The built-in switch stays put. Moving a feature to your service does not turn the built-in email on or off.
The email key
EMAIL_PRIVATE_KEY starts with mek_, and anyone holding it can send email as your site. Both values appear in the Secrets tab with a Managed badge, and neither you nor the AI can edit or delete them.
API access on the Settings tab is the only place the key and the address it goes with appear. It holds three rows:
- API URL, the value of
EMAIL_URL, with a copy icon. It sits on your site's own CDN address, for examplehttps://your-cdn-link/email, once that address has been checked, and on the Modulify API until then. Both keep working, so a live site that was published with the older address still sends - Email Key, the key itself, masked, with three icons beside it, and a note of when it was last created or rotated
- Send an email, a code example to copy, closed until you open it
On Email Key, the eye shows the key and hides it again, and the copy icon copies it, which Copied confirms: "Email key copied. It clears from your clipboard in a minute for security." The rotate icon replaces it, and asks first, Rotate the email key?: "The current key stops working at once. The preview gets the new key right away, but the live site keeps the old key until you publish it again, so its emails fail until then." So publish right after. Without the Delete projects permission the rotate icon is disabled, with the tooltip "You do not have permission to do this."
A project that changes workspace, because it was transferred or moved into another workspace on its own or with its folder, gets a new key the same way, so the workspace it left keeps no working key. Publish after the move so the live site picks the new one up. Its forwarding addresses start over with the new workspace owner's account email, and its email domains are taken away from it, so it sends from its built-in address until someone in the new workspace adds them again, see One site per domain.
What counts as a send
A recipient is a send. One message to five people uses five of the allowance, and to, cc and bcc all count the same. An address that appears on a message more than once counts once. A single message carries at most 50 recipients.
An email that chat or an AI client sends for you counts like any other. A test email never counts. A recipient skipped because it is on the blocked list does not count. A message the mail provider refused to take, or could not be handed to, gives its sends back.
Limits
| Limit | Value |
|---|---|
| Included sends, Free | 50 per site per calendar month |
| Included sends, Starter | 10,000 per site per calendar month |
| Included sends, Pro | 30,000 per site per calendar month |
| Included sends, Enterprise | 50,000 per site per calendar month |
| Extra sends on a paid plan | 8 credits per block of 2,000 on Starter and Enterprise, 4 on Pro |
| Test emails | Free, one every 10 seconds per site |
| Recipients on one message | 50 |
| Reply-to addresses on one message | 10 |
| Sends a minute, per site | 60 |
| Subject | 300 characters |
html or text body |
400,000 characters each |
| Attachments | 10 per message, 7 MB in total |
| Custom headers | 10 per message |
| Tags | 10 per message |
| Clicked links listed on one tracked email | 50 |
| History kept | Until you remove an email or clear the history |
| Email domains per site | 3, added on a paid plan |
The allowance is counted per site and per calendar month, in UTC. It starts again at zero on the 1st, and the usage card names the date, for example Resets on Oct 1, 2026. Included sends you did not use do not carry over.
The included sends cost no credits. On a paid plan, crossing them is not an error. The next block of 2,000 is unlocked for you the moment a send needs it, and its credits are taken from the workspace at that point, then again for each further block. How many credits a block takes follows your plan's credit rate: 8 credits on Starter and Enterprise, 4 credits on Pro, where a credit is worth twice as much.
A block you bought is yours until it is used. Sends left over from a block at the end of the month carry over to the next one, are used after that month's included sends, and keep carrying over until they are gone, so a block bought on the last day of the month is not wasted. The usage card shows them under Included this month, for example +1,500 carried over.
If the workspace moves to a smaller plan during the month, the bigger allowance stays until the month ends, so the sends already made never turn into a charge. The smaller allowance starts on the 1st. A bigger plan applies straight away.
On Free there is no block to buy. A message that needs more sends than are left is refused whole, and nobody on it gets it: This site has 3 emails left of the 50 emails it can send this month, which is not enough for this email. Sending more requires a paid plan!
When the workspace runs out of credits
When a send needs a new block and the workspace does not have the credits for it, sending pauses for the whole site:
- Every send is refused with
403and the codeno-credits, for exampleThis site has 0 emails left this month, and the workspace does not have the 8 credits needed to unlock another 2,000 emails!Nothing is queued, so a refused email is not sent later on its own. - The Emails tab shows Sending is paused until credits are added, with how many emails were refused and since when, and an Add credits button. Only the workspace owner can add credits, so for everyone else the button is disabled with the tooltip "Only the workspace owner can add credits."
- The workspace owner gets an email naming the site, with a link to its Emails tab. It goes out once when sending pauses, and at most once a day for the same site.
Sending picks up again on its own. Once the workspace has the credits, the next email the site sends buys the block and goes out, and the alert clears. In the meantime the tab reads Sending resumes with the next email. A new month starts the count again as well.
Before it comes to that, while 2,000 or fewer sends are left and the workspace has less than one block's worth of credits, the tab shows Sending will pause soon with the same Add credits button, so you can top up before any email is refused.
A free site that uses up its emails pauses the same way, with Sending is paused until next month, a See plans button and the same email to the owner, and it sends again on the 1st or as soon as the workspace moves to a paid plan.
The per minute limit counts recipients too. Past 60 in a minute the site is asked to wait, with This site sent more than 60 emails in a minute. Wait a minute and send again! and a Retry-After header naming the seconds. Nothing is lost; send it again after the wait.
This month on the Emails tab shows the emails sent against the allowance, how many are left and the reset date, above Included this month, Extra blocks bought with what one block costs, for example 8 credits per 2,000 emails, Recipients per email and Emails per minute. On Free the extra blocks read Needs a paid plan, and the card says what a paid plan changes.
Credits spent on email
Credits on the Insights tab follows the tab's date picker. Credits spent and Extra emails add up that period after any refunds, and Credits over time draws it.
Credits on the History tab lists every block of extra emails the site unlocked, newest first, each one as 2,000 extra emails unlocked with its date and time and the credits it took. It starts at Charges and refunds for Last day, and you can keep only Charges or Refunds and pick any other period, All time included. When two sends unlock a block at the same moment, only one block is kept, and the other charge comes straight back as a second row marked Refunded. Load more reaches older blocks. On Free the list stays empty and says Free plans cannot buy extra emails.
The same charges appear, next to everything else the workspace spends credits on, on the Usage tab of the Plans page. See See where your credits went.
The history
The History tab switches between Sending, Opens and clicks and Credits, next to the filters. Insights has the same three, and the one you pick stays picked when you move between the two tabs. Every list also has the date picker the Analytics tab uses, starting at Last day, and the period you pick is shared with Insights. With nothing in that period, the list says Pick a longer period to see more.
Sending lists every message the site sent, newest first. A row shows the status, the subject, a Test marker for a test email, a paperclip with the number of attachments, the first recipient with how many more there were, and the reason when something went wrong. An email sent with open and click tracking on also shows an envelope with its number of opens and a pointer with its number of clicks. The list refreshes on its own as delivery news arrives and checks again every 15 seconds while the tab is open, so there is nothing to reload by hand.
Above the list you can keep only Delivered, In progress or Failed emails instead of Every email, keep only Opened, Clicked or Not opened emails instead of Any engagement, switch Newest first to Oldest first, and search by recipient address. A full address matches exactly, and part of one, such as a domain, matches every address containing it. Load more reads the next page.
Opens and clicks lists the emails sent with tracking on, with the same rows, starting at Opened or clicked and Newest first. You can keep only Opened, Clicked or Not opened emails, or show Every tracked email, and sort by Oldest first, Most opens or Most clicks. The search matches part of a recipient address or part of a clicked link, so typing a page name finds every email where someone clicked a link to it. Load more reads the next page, back to the oldest email the history keeps. Credits works the same way for the blocks of extra emails the site bought.
Every message has a status, and so does every recipient on it.
| Message status | What it means |
|---|---|
| Pending | Being handed to the mail provider |
| Sent | The mail provider accepted it |
| Delayed | Delivery to at least one recipient is being retried |
| Delivered | Every recipient's mail server accepted it |
| Partly delivered | Some recipients got it, and at least one did not. An address skipped because it was blocked does not count here |
| Bounced | Nobody got it, and at least one address refused it, for good or for now, or the mail provider already refuses mail to it |
| Marked as spam | Somebody who got it reported it |
| Rejected | The mail provider refused it before it went out |
| Failed | It never went out, and the row carries the reason |
A recipient reads Pending, Sent, Delivered, Delayed, Bounced, Marked as spam, Rejected, Failed or Suppressed. Suppressed means it was skipped because the address is on this site's blocked list, or because the mail provider already refuses mail to that address for everyone. A temporary bounce, a full mailbox for example, reads Bounced with a detail starting Temporary bounce, so the address was not blocked., and does not block the address. Delayed means the mail provider is still retrying that recipient.
Delivered is the strongest signal there is that a message arrived. It does not tell you anybody read it, and with tracking on an open is only a hint, see Track opens and clicks.
Open an email
Click any row, or View details in its menu, to open the email. The window is titled with the subject and shows its status and a Test marker when it was one. A red This email hit a problem alert gives the reason when something went wrong. Below them the detail is split into five tabs, and every email opens on Overview. An open email keeps itself up to date as its status changes.
- Overview, the Subject, From, To, Cc, Bcc and Reply-to, then Created, Sent and Delivered, the times it was written, handed to the mail provider and accepted, Sent by, which reads Your site, Dashboard, Chat, MCP client or API, the Message ID, the mail provider's own id, the Idempotency key when the send had one, which every send from the site's helper does, and the Tags, Attachments and Custom headers when the message had any. Attachments list the file name, type and size only, because the files themselves are never stored
- Recipients, a table of every address with its kind (To, Cc or Bcc), its status and the provider's detail, such as a bounce reason
- Preview, with HTML and Text tabs
- Engagement, the opens and clicks of an email sent with tracking on, or Not tracked when it was not, see Track opens and clicks
- Timeline, every delivery event with its own time
The HTML preview runs with scripts switched off, so opening an email is always safe. It also blocks remote images, stylesheets and fonts, and says so under the preview: "Remote images and styles are blocked, so viewing an email here never counts as an open." Load remote images loads them for that email. The first 200,000 characters of each body are kept, and a longer one says the end is cut off there, while the recipient got the full email. Emails sent before bodies were kept show No preview.
Remove from history in a row's menu hides that one email, here, in the insights and from what getEmail and listEmails return, and it is erased for good 30 days later. The email itself was still sent and the allowance is not affected.
Read the insights
The Insights tab covers one period across the whole site, picked next to Sending, Opens and clicks and Credits with the date picker the Analytics tab uses: Today (from midnight UTC), Last day (the default, the last 24 hours), Last week, Last month, Last 3 months, Last year, All time or Custom range. The line under the title describes the one you are on. Sending shows Sent, Delivered, In progress and Failed, then Emails over time, a chart split the same three ways. It draws one bar per hour for Today, Last day and a custom range of a single day, one per day for Last week, Last month, Last 3 months and custom ranges up to 92 days, and one per month for anything longer. Credits shows the credits spent on email in the same period. Hours, days and months with no email show as zeros, and they are counted in UTC. The tab keeps itself up to date while it is open, checking again every 15 seconds. Failed gathers every message that did not fully arrive: partly delivered, bounced, marked as spam, rejected and failed.
These count messages, while the allowance counts recipients, so one message to five people is one here and five on the This month card of the Emails tab. Test emails are left out, and the line under the tab's title says so. An email removed from the history leaves these counts too.
Opens and clicks covers the emails sent with tracking on, see In the insights. The same tab on History lists those emails one by one.
Track opens and clicks
Tracking on the Settings tab holds one switch, Track opens and clicks. It is on by default, so a site tracks its emails from the start until somebody turns it off. Turning it off shows Open and click tracking off, and emails already sent with tracking keep reporting their opens and clicks. Turning it back on shows Open and click tracking on: "New emails carry a tracking image and tracked links. Let your recipients know, for example in your privacy policy."
With it on, every email sent is changed on its way out in two ways:
- An invisible tracking image is added to the HTML body. When a mail app loads it, that counts as an open.
- Every link in the HTML body goes through a tracking address first, which counts the click and then sends the reader on to the real page.
The text body is never changed, so an email with only a text body is never tracked, and neither is a test email or any email sent while the switch was off.
To keep one link out of it, add the ses:no-track attribute to that link:
<a ses:no-track href="https://example.com/reset-password?token=...">Reset your password</a>Do this for password reset links, sign in links and any other link that carries a token or something personal, because the full address that was clicked, query string included, is kept with the email and shown in its detail. Chat adds it to those links when it writes the sending code for you.
Opens are approximate, because some mail apps load images automatically and others block them. An email can show an open nobody made, and an email somebody read can show none. Treat opens as a trend across many emails, never as proof that one person read one email.
Opens and clicks belong to the whole email, never to one recipient. An email to three people carries one tracking image and one set of tracked links, so the detail can say it was opened but never by whom. Opens and clicks never change an email's status, never block an address and never count towards an automatic switch off.
When tracking is not set up on the platform, the switch is disabled with the tooltip Open and click tracking is not set up on this server yet. A switch that is already on can still be turned off, and while tracking is not set up, new emails go out untracked rather than failing.
Tracking is on unless you turn it off, and it records when your emails were opened and which of their links were clicked. Say so where your recipients can read it, for example in your privacy policy. While the switch is on, the row reminds you: "Let your recipients know that your emails are tracked, for example in your privacy policy."
In the history
A tracked email's row shows an envelope with its number of opens and a pointer with its number of clicks. An untracked email shows neither.
The engagement filter above the Sending list keeps only tracked emails: Opened keeps the ones opened at least once, Clicked the ones with at least one click, and Not opened the ones delivered to at least one recipient and never opened. A later spam report or bounce does not take a delivered email out of Not opened. Any engagement keeps every email, tracked or not. The Opens and clicks list uses the same three, and also sorts tracked emails by Most opens or Most clicks.
In an email
The Engagement tab in an email's detail shows Opens with First opened, which reads Not opened yet until the first one, and Last opened once there is more than one, then Clicks with First click after the first one and Last click once there is more than one. Below them a table lists every clicked link with its Clicks and Last clicked, most clicked first. Up to 50 different links are listed, and clicks on any other link still count in the total. An email sent while tracking was off shows Not tracked: "Tracking was off when this was sent." An email with only a text body shows Not tracked too, with "Emails with only text are never tracked."
The Timeline gains First opened at the first open, and First click on a link, with the link, at the first click on each link. Later opens, and later clicks on a link that was already clicked, raise the counts without adding to the timeline.
In the insights
Opens and clicks on the Insights tab covers the same period:
- Open rate and Click rate, the tracked emails opened at least once, and those with at least one click, as a share of the tracked emails delivered to at least one recipient. An open or a click counts as delivered, and a later spam report or bounce does not take a delivered email out of that share
- Opened and Clicked, how many tracked emails were opened and clicked
- Opens and clicks over time, a chart of those opened and clicked emails by when each email was sent, not when it was opened
- Top links, the 10 links with the most clicks across every tracked email in the period
Like the Sending counts, these count emails, never people, and leave test emails out. When no tracked email was delivered in the period, it shows No tracked emails, with an Open settings button when tracking is off and set up on the platform.
Blocked addresses
Blocked addresses on the Settings tab lists every address and domain this site will not email. An address lands here on its own when a message to it hard bounces, meaning the mailbox does not exist or refused the message for good, and when its owner marks the email as spam. You can add one yourself too: type an address, or a whole domain such as example.com, @example.com or *@example.com, and click Block. Address blocked confirms your site skips that address on every email from now on, and Domain blocked that it skips every address at that domain. A domain blocks exactly that domain, not its subdomains, and shows in the list as *@example.com. Anything else shows "Enter an email address or a domain to block it." under the field, and an entry that is already on the list reads Already blocked and keeps the reason it has.
Every later message to a blocked address, or to an address at a blocked domain, skips it and still goes to everyone else, and a skipped address never counts towards the allowance. A message whose recipients are all blocked is refused with Every recipient on this message is on this site's suppression list, so nothing was sent!
Each row shows its reason, Hard bounce, Marked as spam or Added by hand, with the provider's detail, or where it was added by hand from, and when it was added. Load more reads the next 50.
Unblock in a row's menu lets the site email that address again from the next message on. It asks first, Let this address receive email again?, or Let this domain receive email again? for a domain. For an address that hard bounced or was marked as spam, it warns that sending to it again can hurt delivery. For an address added by hand, it only confirms that your site sends to it again from the next email on. Only unblock a bounced or reported address when you know it works now, because emailing it again is exactly what pushes the rates below back up.
Automatic switch off
Sending also turns itself off to protect every site's delivery. Once a site has sent to 50 recipients in a calendar month, it is switched off when either of these is true:
- at least 5 recipients hard bounced, and that is more than 10% of what the site sent that month
- at least 3 recipients marked an email as spam, and that is more than 0.5% of what the site sent that month
A temporary bounce does not count, and neither does an address the mail provider already refuses for everyone. The counts start again on the 1st, and a bounce reported after the month has turned counts in neither month.
The tab then shows Sending was turned off automatically with the reason, and the workspace owner gets an email naming the site and the reason, with a link to its Emails tab:
- "Sending was turned off because more than 10% of this site's emails bounced. Clean the recipient list, then turn it back on."
- "Sending was turned off because too many recipients marked this site's emails as spam. Only email people who asked for it, then turn it back on."
Turn sending back on in that alert, or the switch, asks first, Turn email sending back on?: sending resumes to the same recipients as before, and if they keep bouncing or reporting spam, sending switches off again automatically. Turning it back on this way starts the counts afresh: from then on only emails sent after it was turned back on are judged, with the same minimums of 50 recipients, 5 hard bounces and 3 spam reports, and a bounce or spam report for an email sent before that never counts. Turning sending off and on again yourself, when it was not switched off automatically, resets nothing. Only this tab can turn it back on. Chat and a connected AI client are refused and told to send you here.
Email people who asked to hear from you: receipts, notifications, confirmations, password resets. A bought list or a newsletter is what produces the spam reports, and the rates above are measured per site.
Forwarding
Every address at your site's built-in domain accepts mail, such as [email protected], and so does every address at each of your email domains with receiving on, such as [email protected]. Modulify forwards each email to the site's confirmed forwarding addresses. That covers the sending address when it is on one of those domains, so a reply to one of your site's emails is forwarded when no reply-to is set, and any other name in front of the @, such as hello or bookings, with nothing to set up. Forwarding is on for every site from the start, and it keeps running while sending is off.
The site never receives the mail itself. There is no inbox in Modulify and nothing your site's code or chat can read.
Forwarding addresses
Forwarding on the Settings tab lists the addresses the site's mail goes to, up to 5. Every site starts with one, the email address of the workspace owner's Modulify account. It is confirmed straight away when the owner's sign-in confirms that address, through a verified email or a Google or GitHub sign-in. Otherwise it shows Not sent until someone selects Send link again in its menu and the owner opens the link. The table shows each address with its status and when it was added. The status reads Verified once the address is confirmed, Link sent while its confirmation link waits to be opened, Link expired once that link is past its 7 days, or Not sent when no link went out, and only verified addresses get mail. Every verified address gets each forwarded email, and the forwarded email keeps its original recipients, so none of the forwarding addresses can see the others.
When the site changes workspace, because it was transferred or moved, its list starts over with the new workspace owner's account email, so nobody from the previous workspace keeps getting its mail. When its workspace gets a new owner, the new owner's account email takes the previous owner's place on every site of the workspace, and the other addresses stay. A person who leaves the workspace, is removed from it or hands it over loses their own account email from its sites' lists, when it got there as the owner's address or because they added their own account email. An address someone else added and its owner confirmed through the link stays.
To add one, type it under the list and select Add address.
- Your own account email is confirmed straight away when your sign-in confirms it, through a verified email or a Google or GitHub sign-in, and so is an address at a domain the site already proved it owns: any of its connected custom domains or one of its verified email domains, subdomains included. A site connected only at
www.example.comcovers addresses atwww.example.comand below it, not atexample.com. - Any other address gets an email from Modulify with a confirmation link, and shows Link sent until someone opens it. The link goes out the moment you add the address and works for 7 days, and after that the address shows Link expired. Send link again in the address's menu sends a new one, and the previous link stops working. When the email could not be sent, the address shows Not sent, and Send link again tries once more.
Opening the link confirms the address straight away, with no sign in needed, on a page that says Forwarding confirmed: "Mail sent to Lisbon Pottery is now forwarded to [email protected]." Under Did not ask for this?, Stop forwarding to me takes the address off the site's list and shows Forwarding stopped. An address stopped this way cannot be added to that site again, and adding it is refused with This address asked not to receive mail from this site, so it cannot be added again! The page keeps working for the 7 days the link is valid, so forwarding can still be stopped from it after it was confirmed. A link that expired, or that belongs to an address that was removed, shows Link no longer valid: "Ask the site for a new link." Opening more than 30 links in 10 minutes from one network shows Too many attempts: "Wait a few minutes, then try again."
Remove in an address's menu asks first, Remove this forwarding address?: "Mail sent to this site stops reaching it straight away." A removed address can be added again.
Adding an address is refused when it is:
- not one valid email address, with
Enter one valid email address! - at a Modulify site domain, such as an address ending in
modulify.website, withMail cannot be forwarded to an address at a Modulify site domain! - at a domain a Modulify site receives mail for, because every forward would come straight back, with
Mail sent to this address comes back to a Modulify site, so it cannot be a forwarding address! - already on the list, with
This address is already a forwarding address of this site!
Once the site has 5, Add address is disabled with the tooltip "This site already forwards to 5 addresses, the most it can have." Only people with the Delete projects permission can add, remove or send a link again. For anyone else those controls are disabled with the tooltip "You do not have permission to do this.", and each address shows with most of it hidden.
While none of the site's addresses is verified, mail sent to the site is dropped. With none at all, the list shows No forwarding addresses: "Mail sent to this site is dropped."
When an address comes off the list
Modulify takes an address off on its own, and tells nobody, when:
- a forward to it bounces for good. When the address does not exist or the mail provider blocks it, it comes off the list of every site that forwards to it.
- its mailbox marks a forwarded email as spam. It comes off that site's list.
A bounce that is only for now, such as a full mailbox, takes nothing off. An address that came off can be added again, and it gets a new confirmation link unless it is confirmed straight away.
An address at a domain that a Modulify site receives mail for gets no mail while that lasts, because every forward would come straight back. For the same reason, a site cannot turn on receiving for a domain one of its own forwarding addresses is at.
What a forwarded email looks like
A forwarded email does not come from the person who wrote it. It comes from an address at a separate Modulify forwarding domain, made of the site's prj- id and a short code for the original sender, like [email protected], and the sender reads as the original sender, via, then your site's sender name, for example Jane Doe via Lisbon Pottery, or jane at example.com via Lisbon Pottery when they gave no name. The subject, the text and the attachments arrive as they were sent.
Each original sender gets a forwarding address of their own, so blocking a forwarded email in your mail app blocks only that sender. To stop all of the site's forwarded mail, turn off Forward incoming mail, see Turn forwarding off and on.
Its reply-to is the original email's reply-to, with every address when it had several, or the original sender when it had none, so replying answers the right person. It also keeps a link to the original message, so replies thread with the original conversation. A reply sent to the forwarding address itself is not delivered anywhere.
A reply to a forwarded email goes out from the mailbox that got it, so the person who gets the answer sees that mailbox's own address, not the site's.
Turn forwarding off and on
Forward incoming mail, at the top of Forwarding on the Settings tab, turns all of it on or off. Turning it off asks first, Turn off forwarding?: "Mail sent to this site, including mail still waiting to be forwarded, is dropped while forwarding is off and is never forwarded later." Only people with the Delete projects permission can change it, and for anyone else the switch is disabled with the tooltip "You do not have permission to do this."
While forwarding is off, mail to the site is still accepted and then dropped. Mail that was still waiting to be forwarded when it was turned off is dropped too, and a forward that was being processed but not sent yet is stopped. The sender gets no bounce and no error, so nothing tells them the email went unread. Turning forwarding back on forwards what arrives from then on, never mail that was dropped while it was off.
When forwarding is not set up on the platform, a switch that is on can still be turned off. A switch that is off is disabled with the tooltip Email forwarding is not set up on this server yet. Mail sent to the site while forwarding is not set up is not forwarded.
Chat and a connected AI client can tell you whether forwarding is on or off and which addresses get the mail, and change both when you ask, with the same Delete projects permission. They ask you first before turning forwarding off or removing an address. See What the AI can do.
What is never forwarded
These are dropped: never forwarded and never tried again.
- An email with a virus, and one that could not be scanned for viruses or spam
- Spam, and borderline mail that passes neither of the SPF and DKIM checks that show where an email came from
- An email that fails its sender's DMARC check when the sender's domain asks for such mail to be quarantined or rejected
- An email that claims to come from a Modulify site address but was not signed by one
- An email with no return address
- Bounce and delivery reports sent by mail systems
- Automatic replies, such as out of office messages
- A forwarded email that comes back to a site, so two addresses can never forward to each other in a loop
- Mail to a site that was deleted, or to one nobody has claimed yet
- An email over 25 MB, and one the mail provider refuses to pass on, often because it carries a blocked file type
- Mail sent to one of your email domains that no longer reaches the site by the time it would be forwarded, because the domain was removed or moved to another site, its receiving was turned off or it stopped being verified
- Mail that arrives while forwarding is off, and mail still waiting to be forwarded when forwarding is turned off
- Mail for a site with no verified forwarding address, or whose verified addresses are all at a domain a Modulify site receives mail for, see Forwarding addresses
An email addressed to more than 3 different sites at once is treated as spam and forwarded to none of them.
None of these send anything back. Modulify accepted each one, so the sender is not told it was dropped, and nothing tells you either.
When mail waits
Mail is held instead of dropped when:
- the site, the sender or one of the site's forwarding addresses is over one of the forwarding limits
- the platform cannot send forwards at that moment
Held mail is tried again every 10 minutes, oldest first, so it goes out soon after the reason clears. Mail still waiting 6 days after it arrived is given up on. A forward that could not be handed to the mail provider is tried again as well, up to 5 attempts in all, before it is given up on. A forward the mail provider has not confirmed is sent again when no confirmation arrives within 30 minutes. Those sends count toward the same 5 attempts and 6 days, and past either limit the email is given up on.
If Modulify itself is briefly unreachable when mail arrives, the mail is not lost: it is picked up once Modulify is back and forwarded then, as long as that is within 6 days of its arrival.
Forwarding limits
| Limit | Value |
|---|---|
| Forwards from one sender to one site | 10 an hour |
| Forwards for one site | 30 an hour, 200 a day |
| Forwards to one forwarding address, across every site it gets mail from | 100 an hour, 500 a day |
| Sites one email can be addressed to | 3 |
| Size of one email | 25 MB |
| Longest wait for held mail | 6 days, tried every 10 minutes |
The first three hold mail back rather than drop it, and while one of the site's addresses is over its limit, the email waits for all of them. A sender is counted by the domain of their address, so everyone writing from example.com shares the same 10 an hour, except at large shared mail providers such as Gmail, Outlook, Hotmail, Live, Yahoo, iCloud, AOL, Proton and GMX, where each address counts on its own. Mail from the site's own sending address, such as a contact form notification the site sends to itself, has no sender limit, only the limits for the site and its forwarding addresses. An hour and a day are counted back from the moment an email is tried, not by the clock or the calendar. Modulify also caps how many emails it forwards across the whole platform each hour and each day, and mail over that cap waits the same way.
Forwarding addresses and their confirmation links have limits of their own:
| Limit | Value |
|---|---|
| Forwarding addresses per site | 5 |
| Addresses one workspace can add | 30 an hour |
| Confirmation links to one address | 1 a minute from the same site, 5 a day across all sites |
| Confirmation links one site sends | 20 a day |
| Confirmation links one workspace sends | 50 a day |
| How long a confirmation link works | 7 days |
| Confirmation links opened from one network | 30 every 10 minutes |
Adding an address, or sending a link again, over one of these limits is refused, and the message says how long to wait, for example A confirmation link just went to this address. Try again in 45 seconds! Every try to add an address counts toward the 30 an hour, refused ones included. An address confirmed straight away sends no link, so the link limits do not apply to it.
Danger zone
Two rows sit at the bottom of the Settings tab.
Export .json under Export history downloads every email in the history as one JSON file, however long the history is, named after the site's subdomain and the day, like lisbon-pottery-emails-2026-09-29.json. Each email carries the subject, the recipients, cc, bcc, the status and every recipient's delivery status, and message bodies are never included. Emails exported confirms the download, and a site with no emails shows Nothing to export. One export runs per site at a time, so asking for another while one is still running shows Could not export the emails with An export of this site's emails is already running. Wait for it to finish, then export again!
Clear history asks first, Clear the email history?, then removes every row from the History tab, from the insights and from what getEmail and listEmails return. The cleared rows are erased for good 30 days later. Sending keeps working and this month's allowance is not affected.
What the AI can do
The AI in the editor chat can do almost everything in this tab with you, and send email when you ask it to.
- Read the setup. Whether sending is on, the sender name and whether it follows the site's name, the exact sending address and whether it is on the built-in domain or one of your email domains, each email domain with its status, whether the site sends from it and whether it receives mail, whether the workspace can add one, where replies go, this month's usage with what is left and the reset date, the limits, whether open and click tracking is on, whether the live site still needs a publish, why sending was switched off if it was, and whether forwarding is on, off or not set up, with the addresses it goes to. It also reads the blocked addresses, with why and since when each one was blocked, and can check one address.
- Read the history. It lists the messages the site sent, filtered by status, recipient or engagement, and opens any one of them to say who got it, who did not and why, and what it said. For a tracked email it also reads the opens, the clicks and which links were clicked, and with no filter set, the open and click rates and the most clicked links for the last 30 days, always as counts for the whole email and never as proof that a particular person read it.
- Send an email for you. When you ask it to send a specific email now, to people you name, it sends one from the site's own address. It never picks, looks up or invents a recipient, never sends bulk or marketing mail, and shows you the text first when it wrote it. That email counts against the allowance like any other, and its detail shows Chat under Sent by.
- Send a test email to your own account address. It is free, exactly like a test sent from this tab.
- Change the settings. It turns sending on and off, and changes the sender name, the sending address and the reply-to. Changing the part in front of the
@while one of your domains is chosen as the sending domain needs the Manage domains permission, like in this tab. Ask for the site's name back as the sender and it clears the custom one. It asks before turning sending off or changing the sending address, and before turning sending back on after anyone turned it off, and tells you when the live site needs a publish. Asked about tracking, it tells you open and click tracking is on by default. It turns tracking off when you ask and back on only when you ask, and turning it on is refused withOpen and click tracking is not set up on this server yet!when tracking is not set up on the platform. - Block and unblock addresses. It blocks addresses or whole domains you name, and unblocks only ones that were added by hand, which also needs the Delete projects permission. An address that bounced or reported spam stays for you to decide here.
- Manage your email domains. It adds a domain you name on a paid plan and gives you the DNS records to add, or the one-click setup link when your DNS provider supports it. It checks a domain again, makes the site send from it or from the built-in address again, turns its receiving on or off, restarts a failed verification and removes it, asking you first before anything that removes a domain, switches the sending domain or moves mail. The same permissions apply as in Email domains, and a paused domain can be removed but not chosen to send from or have its verification restarted.
- Manage forwarding. It turns forwarding on or off, adds the addresses you give it, which get their confirmation link straight away unless they are confirmed at once, sends a link again and removes an address, asking you first before turning forwarding off or removing an address. It needs the Delete projects permission, like Forwarding.
- Tidy the history. It removes emails from the history or clears it, after asking you first, with the Delete projects permission.
- Write the sending code. When the site itself should send, from a contact form or a checkout, chat writes that code with the site's email helper, adds the helper first when the site does not have it, and puts
ses:no-trackon links that carry a token. - Notify you about forms. A form someone has to act on, such as a contact, booking or quote form, emails you each submission by default. The site sends it to its own sending address, so forwarding delivers it to your forwarding addresses, or, when the site sends from one of your domains without receiving its mail, that domain's own mailboxes get it. The visitor's address is the reply-to, so answering goes straight to them. Ask for another address and it sends there instead.
- Use your own mail service. It sets up Resend, SendGrid or another mail service of yours when you ask, after recommending the built-in email once. See Use your own mail service.
It is never handed the site's email key, so showing and copying it stay in this tab, with downloading the history. It can rotate the key when you ask, which needs the Delete projects permission, and then reminds you that the live site keeps the old key until you publish again. It cannot turn sending back on after an automatic switch off, and an address that bounced or reported spam stays for you to unblock. On Free it says that adding a domain needs a paid plan and that a mail service of your own works as well. See What chat can and cannot change.
From an AI client over MCP
Everything in this tab is reachable from a connected AI client through 30 tools:
get_site_email_settings,get_site_email_usage,list_site_email_sends,get_site_email,get_site_email_stats,list_site_email_engagement,list_site_email_credits,get_site_email_credit_statsandlist_site_email_suppressionsread the settings, the allowance, the history, one email in full, the counts over time, the opens and clicks, the credits spent and the blocked addresses, and needconfig:readtoggle_site_emails,update_site_email_settings,send_site_email,send_test_site_email,block_site_email_address,delete_site_email_suppression,delete_site_email_send,clear_site_email_historyandrotate_site_email_keychange them, and needconfig:writeadd_site_email_domain,check_site_email_domain,remove_site_email_domain,set_site_email_sending_domain,set_site_email_domain_receiving,restart_site_email_domain_verificationandget_site_email_domain_connect_urladd, check and remove email domains, choose the domain the site sends from, turn receiving on or off, restart a verification and set a domain up in one click, and needconfig:writeset_site_email_forwarding,add_site_email_forward_address,resend_site_email_forward_addressandremove_site_email_forward_addressturn forwarding on or off and manage the forwarding addresses, and needconfig:writeget_site_email_keyreads the email key in plain text, and needscredentials:reveal
config:write and credentials:reveal are unticked by default. update_site_email_settings changes the sender name, where an empty fromName goes back to the site's name, and turns open and click tracking off and on with trackEngagement. list_site_email_sends takes the same engagement filter as the History tab. send_site_email sends one email to recipients the person named, and its detail shows MCP client under Sent by, or API when it was sent through the HTTP API. send_test_site_email is free, like a test sent from this tab. delete_site_email_suppression, delete_site_email_send, clear_site_email_history, rotate_site_email_key and the forwarding tools also need the Delete projects permission, like in this tab, and toggle_site_emails cannot turn sending back on after an automatic switch off. Downloading the history as one file stays in the danger zone of this tab; a client reads the same history page by page with list_site_email_sends.
The domain tools meet the same rules as Email domains. add_site_email_domain needs a paid plan and the Manage domains permission, remove_site_email_domain, set_site_email_sending_domain and restart_site_email_domain_verification need that permission on any plan, though the last two refuse a paused domain, set_site_email_domain_receiving needs the Delete projects permission, and check_site_email_domain is open to any member. get_site_email_settings reports the site's domains with their records in emailDomains, the address the site sends from right now in fromAddress and its built-in domain in sendingDomain. While the domains are paused, emailDomains.paused and the paused of each domain are true, and so is fromPaused when the chosen sending domain is one of them. It also reports forwarding in forwarding, with every address pattern that forwards in forwarding.addresses and the forwarding addresses with their ids in forwarding.recipients. Only get_site_email_key returns the key. See MCP tools.
Next
- Email HTTP API is the endpoint behind the helper, with every field and every refusal a caller can branch on.
- Secrets covers
EMAIL_URLandEMAIL_PRIVATE_KEYand the rest of the managed values. - Credits explains what a block of extra sends costs and where it comes from.
- Webhooks tells your own systems when the site publishes.