Imagine this: your email is done and looks equally great on every device. Except that's far from reality — different email clients, such as Gmail, Yandex.Mail, and others, render the same HTML code differently. That's why an email developer's job isn't just to turn a template design into code, but to make sure the email looks good everywhere. That's what we'll talk about.
Table-based layout
Building HTML markup for email campaigns isn't the same as building HTML markup for a website or landing page. In email, only table-based layout ensures a campaign displays properly. That's because emails can be opened in email clients and browsers old enough not to support newer versions of HTML. So emails built with table-based layout are guaranteed to look good everywhere.
Essentially, HTML markup for an email campaign is a set of tables nested inside other tables. We took a look at the markup and used the internal border values, setting them to 1:
Features and limitations of email markup
The <style> tag
This tag defines the CSS styles for the HTML markup of a campaign — fonts, colors, the placement of individual blocks, and other visual elements.
Important: not all email clients support the style tag (here's a simple table that shows this). That's why media queries can also cause problems — they won't work everywhere.
Shorthand style notation
Since not all email clients support the <style> tag, all styles need to be inlined — written in the attribute, and in full form rather than shorthand. Compare the two versions:
Shorthand form
h1 {
color: #fff;
}
Full form
h1 {
color: #ffffff;
}
We need the second one. By the way, you can use inliners to inline styles and "convert" shorthand notation into full form — this kind of tool is already built into Letteros.
Fonts
Fonts in email markup are a case where it's best not to stray from the classics and stick to standard web fonts. Here's the standard list of fonts used for HTML emails:
font-family: Arial, Helvetica, sans-serif;
font-family: Arial Black, Gadget, sans-serif;
font-family: Georgia, serif;
font-family: MS Sans Serif, Geneva, sans-serif;
font-family: MS Serif, New York, sans-serif;
font-family: Tahoma, Geneva, sans-serif;
font-family: Times New Roman, Times, serif;
font-family: Trebuchet MS, Helvetica, sans-serif;
font-family: Verdana, Geneva, sans-serif.
If you connect other fonts, keep in mind that this will only work in email clients that support the <style> tag.
HTML code size
Good to know: Gmail clips emails whose HTML code exceeds 100 KB. Users will then see the message "Message clipped" along with a link to view the full text:
To keep emails from being clipped, their code needs to be trimmed to under 100 KB. You can use a code minifier for this — it compresses the code into a single line and removes all unnecessary characters. This can save around 20% in kilobytes.
Images
To make sure all images (including background images) in an email display correctly, the main rule is to only use these formats — jpg, gif, and png.
There are no technical limits on image size in email templates. But on mobile internet, very "heavy" images will take a long time to load after a user clicks the subject line. That's why banner images should weigh no more than 150 KB, and all other images no more than 50 KB.
To compress an image down to the needed size, you can use dedicated tools — Optimizilla and TinyPNG. Or the built-in tool in Letteros.
JavaScript
Email clients don't allow developers to use JavaScript. But you can add interactive elements to an email sequence using AMP technology. You can't insert your own JavaScript into AMP emails, which means they stay safe. Here's a useful article that will introduce you to the technology.
AMP doesn't work everywhere yet: Gmail, Mail.Ru, and Yahoo! Mail have already adopted the technology, but Yandex hasn't, so AMP functionality won't display in its mail services.
Surveys
Emails sometimes include surveys, which in turn have checkboxes, checkmarks, input fields, and other elements not supported in email markup. So how do you build them?
The first method is an NPS survey with a single closed-ended question, something like "Rate our service from 1 to 10." The results show how the subscriber rated your service after reading your email:
The second method works for open-ended questions: a button (or any other spot) in the email links out to a survey on Google Forms.
The third option is something in between the first two. You simply add a question with several answer buttons to the email, and the user is taken to a website where the first question is already answered but a few more are still waiting.
Adding to a calendar
A subscriber can add an event from an email straight to their calendar — here's how it works:
- They receive an email announcing the event.
- They click the "Add to calendar" button.
- Their calendar opens automatically and offers to add the event.
To make this work, you need to make the calendar public, open access to the event for everyone, copy the link, and finally send it to your users. This works with all popular calendar types: Google, Yandex, Yahoo!, Mail.ru, Outlook, or iOS. See this article for detailed instructions.
Countdown timers
A countdown timer is a great tool for reminding readers in a promo email sequence: time is running out.
For the timer to work correctly, it's best built with a script — either your own or using services like Sendtric or MotionMail.
Spacing in email markup
Spacing is one of the fundamental elements of markup, and emails often run into problems with it. So let's talk about that too.
The padding property sets inner spacing on all sides of an element. But vertical spacing inside the body structure can also be set using an empty table with a specified height for the gap.
<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td height="10" style="font-size: 0; line-height: 10px;"></td></tr></table>
Set font-size to zero. Another option is to mark the spacing with a line, like this:
Its code:
<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td align="left" valign="top" style="border-top-width:1px;border-top-style:solid;border-top-color:#E7E7E7;"></td></tr></table>
This is a table with no dimensions at all. The border-top-width value defines a 1px line in the gray color set by border-top-color, and the border-top-style value gives the line a solid style. There are a few more values you can use:
- none removes the line;
- dotted is made up of a series of dots;
- dashed produces a dashed line;
- double creates a double line;
- groove creates a carved-in line effect;
- ridge creates a raised line effect;
- inset and outset create a 3D line effect.
Margin, on the other hand, defines the outer spacing on all four sides of an element in CSS:
margin-top, margin-right, margin-bottom, and margin-left.
Now for the most interesting part — mobile adaptation.
Mobile adaptation
Responsiveness is the ability of an email to adjust to screen width, so the text stays readable and images stay the right size.
To achieve this, you need to set the size of each block in percentages instead of pixels — this way, the HTML markup of the email will adapt to different screens. This approach is called fluid.
Stacking blocks
Images on mobile devices shrink proportionally to screen width. Here's how it works:
In responsive markup, the email's elements stack on top of each other by default on a mobile screen:
And just like that, the image moves below the text.
Two stacking blocks
Essentially, two stacking blocks are two div elements with the display property set to inline. This makes the element render as an inline block.
Inline blocks take up the full specified width of their container. So if the email width is 600px, two equal-sized containers will each be 300px. Here's a code example:
<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td align="center" valign="top" style="font-size: 0px;"><!-- Item --><div style="display: inline-block;vertical-align:top;width:300px;"><table width="100%" border="0" cellspacing="0" cellpadding="0" style="border-collapse:collapse;"><tr><td align="center" valign="middle" style="font-size: 14px;"><table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td align="center" valign="top">контент</td></tr></table></td></tr></table></div><!-- Item END--><!--[if (gte mso 9)|(IE)]></td><td valign="top" style="width: 300px;"><![endif]--><!-- Item --><div style="display: inline-block;vertical-align:top;width:300px;"><table width="100%" border="0" cellspacing="0" cellpadding="0" style="border-collapse:collapse;"><tr><td align="center" valign="middle" style="font-size: 14px;"><table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td align="center" valign="top">контент</td></tr></table></td></tr></table></div><!-- Item END--></td></tr></table>
Naturally, we zero out cellpadding and cellspacing and set td align to center. The code for the first block is closed with a conditional comment for Outlook — even though it doesn't actually need the stacking, since it's a desktop client. But without it, Outlook won't see the closing </td> tag in the first container.
The second block is nearly identical, just with its own conditional comment for Outlook.
In the end, the table looks like this:
content | content |
|---|
And if the screen narrows by even a single pixel, the blocks will stack. Example:
content |
|---|
content |
If you need to set different cell widths, do this:
<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td align="center" valign="top" style="font-size: 0px;"><!-- Item --><div style="display: inline-block;vertical-align:top;max-width:400px;width:100%"><table width="100%" border="0" cellspacing="0" cellpadding="0" style="border-collapse:collapse;"><tr><td align="center" valign="middle" style="font-size: 14px;"><table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td align="center" valign="top">контент</td></tr></table></td></tr></table></div><!-- Item END--><!--[if (gte mso 9)|(IE)]></td><td valign="top" style="width: 200px;"><![endif]--><!-- Item --><div style="display: inline-block;vertical-align:top; width:200px"><table width="100%" border="0" cellspacing="0" cellpadding="0" style="border-collapse:collapse;"><tr><td align="center" valign="middle" style="font-size: 14px;"><table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td align="center" valign="top">контент</td></tr></table></td></tr></table></div><!-- Item END--></td></tr></table>
Here, width becomes max-width because the size is now larger than 300px: if you hard-code the width, the screen would be forced to 400px. Now our table looks like this:
content | content |
|---|
And here's how it looks after stacking:
content | |
|---|---|
content |
The same goes for banners. Banner content is often split into two sides: text on the left, image on the right:
When adapting, a banner can stack in different ways — the right column can end up moving below the other:
But it can also work the other way around — text first, then the image. This brings us to reverse block stacking.
Reverse block stacking
This kind of email markup works pretty much the same way, but with one catch: the dir attribute. It's used to set the direction:
- ltr (Left To Right) — text is displayed left to right;
- rtl (Right To Left) — right to left.
The code starts with table border, then tr serves as the container for creating a table row:
<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td dir="rtl" align="center" style="font-size: 0px;"><!--[if (gte mso 9)|(IE)]><table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td width="300" style="width: 300px;" dir="rtl"><![endif]--><!-- Item --><div style="display: inline-block; vertical-align: top; width: 300px;"><table width="100%" cellpadding="0" cellspacing="0" border="0" dir="ltr"><tr><td align="center" style="font-size: 14px;">Блок 2</td></tr></table></div><!-- Item END--><!--[if (gte mso 9)|(IE)]></td><td width="300" style="width: 300px;"><![endif]--><!-- Item --><div style="display: inline-block; vertical-align: top; width: 300px;"><table width="100%" border="0" cellspacing="0" cellpadding="0" dir="ltr"><tr><td align="center" style="font-size: 14px;">Блок 1</td></tr></table></div><!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]--></td></tr></table>
Here, td align horizontally aligns the cell content. Here's an example of how the code renders on a large screen:
Block 1 | Block 2 |
|---|
And here it is on mobile:
Block 2 |
|---|
Block 1 |
As you can see, these are two div tags, but they'll be read in reverse order. Plus, a conditional comment for Outlook was added so it picks up the specified direction.
Image adaptation
There's a rule here: for all images wider than 300px, you don't need to set a fixed height. When height isn't specified in the code, the system calculates it automatically in proportion to the image size.
For an image wider than 300px to adapt properly without breaking the layout, set the width as a percentage (100%) in the styles, along with a max-width for the img (600px), using the fluid approach:
<span style="font-family: Tahoma, Arial, Helvetica, sans-serif; font-size: 16px;color:#000000;"><img src="creative-main.png" width="600" alt="Альтернативный текст" border="0"style="display: block; width: 100%; max-width: 600px"/></span>
Desktop clients (including Outlook) will see width=600 and display the banner at 600px wide. Google reads width: 100%; max-width: 600px and displays the image at full width, but no more than 600px. This achieves scaling of the image without preventing the rest of the email's content from adapting.
By the way, don't forget to add alt text — you can't do without it.
Button scaling
Sometimes buttons stretch to the full width of the screen:
Here's how a button like that scales in the mobile version:
<table width="100%" border="0" cellspacing="0" cellpadding="0" style="width: 100%;"><tr><td align="center" valign="middle" height="50" bgcolor="#000"style="height: 50px; display: block; background-color: #000000;"><!--[if gte mso 9]><table border="0" cellspacing="0" cellpadding="0"><tr><td nowrap align="center" valign="middle" height="50" style="padding: 0 30px;"><![endif]--><a href="#" target="_blank"style="font-family: Arial, Helvetica, sans-serif; font-size: 24px; line-height: 50px; color: #ffffff; white-space: nowrap; font-weight: normal; text-decoration: none; display: block; padding: 0 30px;">Текст кнопки</a><!--[if gte mso 9]></td></tr></table><![endif]--></td></tr></table>
Let's break it down: height and line-height are equal here, just like background and bgcolor. The target attribute is set to _blank, so the page opens in a new browser window.
It's important that the line height matches the button height and that the button text is under 300px wide. If it's wider, the text will either break the adaptation by hitting the button's edges when it narrows [if the white-space property is set to nowrap], or inevitably wrap and spill outside the button.
Dark theme
How does this work? It's simple: when dark mode is turned on, the page background turns dark and the font turns light.
Some email clients apply a direct "white to black" inversion, while others "pick" a dark equivalent instead. So if the background is built as an image, the text color gets inverted, which can affect readability. It's important to note that different services handle dark theme differently, so results will vary.
Take black text on a white background, for example. In dark theme, it gets inverted and everything looks as if it was designed that way. But if the background is built as an image, the black text gets inverted to white in dark theme while the background stays the same. Then the white text on a white background simply disappears.
The only solution to this problem is to add transparency at the design stage. You don't need a separate layout for dark theme, but a designer can double-check themselves by creating a darkened block and seeing how the banner looks against it in dark theme.
Even better — make the background a color that both white and black text read well against:
If the background is built with code, its color will also get inverted. However, some gradients need to be built as an image: a straight two-color gradient is easy to build with code, but if it's more complex, that won't work and you'll need to use a classic banner instead.
Black-and-white or gray icons used in the email design should have an outline matching the background color (1–1.5 px). It won't be visible in light theme, but it definitely will be in dark theme. The same rule applies to non-standard fonts used in headings.
If a button is built with code, it will also get inverted. If the button has an outline in light theme, you can avoid this by building buttons as images with a fill matching the background color. In light theme, this will look like an outline, and in dark theme, like a regular button. We recommend using this technique.
How to make sure your markup definitely doesn't break
There's a solution: build your email sequence in the Letteros editor and use a template for it. It's a modular editor that lets you build an email template's markup without a developer or any HTML knowledge.
The platform shows how an email displays on a huge number (100+) of popular devices, and built-in tools instantly check the email for errors — so the campaign looks great on both desktop and mobile clients: the markup adapts to any type of device. You can also test campaigns and use the service's other features right there.
Check out the demo and switch to Letteros, where building an email takes 15 minutes.
By the way, we'll help migrate your templates for free, and our support team is always available to show you how to get the most out of the editor.